diff --git a/NHSE.Core/Save/Files/MainSave.cs b/NHSE.Core/Save/Files/MainSave.cs index ec3e980..7377da5 100644 --- a/NHSE.Core/Save/Files/MainSave.cs +++ b/NHSE.Core/Save/Files/MainSave.cs @@ -66,29 +66,19 @@ public IReadOnlyList Buildings set => Building.SetArray(value).CopyTo(Data, Offsets.MainFieldStructure); } - public PlayerHouse GetPlayerHouse(int index) + public IPlayerHouse GetPlayerHouse(int index) => Offsets.ReadPlayerHouse(Data, index); + public void SetPlayerHouse(IPlayerHouse value, int index) => Offsets.WritePlayerHouse(value, Data, index); + + + public IPlayerHouse[] GetPlayerHouses() { - if ((uint)index >= MainSaveOffsets.PlayerCount) - throw new ArgumentOutOfRangeException(nameof(index)); - return new PlayerHouse(Data.Slice(Offsets.PlayerHouseList + (index * PlayerHouse.SIZE), PlayerHouse.SIZE)); + var players = new IPlayerHouse[MainSaveOffsets.PlayerCount]; + for (int i = 0; i < players.Length; i++) + players[i] = GetPlayerHouse(i); + return players; } - public void SetPlayerHouse(PlayerHouse h, int index) - { - if ((uint)index >= MainSaveOffsets.PlayerCount) - throw new ArgumentOutOfRangeException(nameof(index)); - h.Data.CopyTo(Data, Offsets.PlayerHouseList + (index * PlayerHouse.SIZE)); - } - - public PlayerHouse[] GetPlayerHouses() - { - var villagers = new PlayerHouse[MainSaveOffsets.PlayerCount]; - for (int i = 0; i < villagers.Length; i++) - villagers[i] = GetPlayerHouse(i); - return villagers; - } - - public void SetPlayerHouses(IReadOnlyList houses) + public void SetPlayerHouses(IReadOnlyList houses) { for (int i = 0; i < houses.Count; i++) SetPlayerHouse(houses[i], i); diff --git a/NHSE.Core/Save/Offsets/MainSaveOffsets.cs b/NHSE.Core/Save/Offsets/MainSaveOffsets.cs index ff536c0..bbd7ee1 100644 --- a/NHSE.Core/Save/Offsets/MainSaveOffsets.cs +++ b/NHSE.Core/Save/Offsets/MainSaveOffsets.cs @@ -48,9 +48,13 @@ public abstract class MainSaveOffsets public abstract int VillagerSize { get; } public abstract int VillagerHouseSize { get; } + public abstract int PlayerHouseSize { get; } + public abstract int PlayerRoomSize { get; } public abstract IVillager ReadVillager(byte[] data); public abstract IVillagerHouse ReadVillagerHouse(byte[] data); + public abstract IPlayerHouse ReadPlayerHouse(byte[] data); + public abstract IPlayerRoom ReadPlayerRoom(byte[] data); public static MainSaveOffsets GetOffsets(FileHeaderInfo Info) { @@ -162,5 +166,23 @@ public void WriteVillagerHouse(IVillagerHouse v, byte[] data, int index) var size = VillagerHouseSize; v.Write().CopyTo(data, NpcHouseList + (index * size)); } + + public IPlayerHouse ReadPlayerHouse(byte[] data, int index) + { + if ((uint)index >= PlayerCount) + throw new ArgumentOutOfRangeException(nameof(index)); + + var size = PlayerHouseSize; + var v = data.Slice(PlayerHouseList + (index * size), size); + return ReadPlayerHouse(v); + } + + public void WritePlayerHouse(IPlayerHouse v, byte[] data, int index) + { + if ((uint)index >= PlayerCount) + throw new ArgumentOutOfRangeException(nameof(index)); + var size = PlayerHouseSize; + v.Write().CopyTo(data, PlayerHouseList + (index * size)); + } } } diff --git a/NHSE.Core/Save/Offsets/MainSaveOffsets10.cs b/NHSE.Core/Save/Offsets/MainSaveOffsets10.cs index d0e20fd..da78065 100644 --- a/NHSE.Core/Save/Offsets/MainSaveOffsets10.cs +++ b/NHSE.Core/Save/Offsets/MainSaveOffsets10.cs @@ -52,5 +52,11 @@ public class MainSaveOffsets10 : MainSaveOffsets public override int VillagerHouseSize => VillagerHouse1.SIZE; public override IVillagerHouse ReadVillagerHouse(byte[] data) => new VillagerHouse1(data); + + public override int PlayerHouseSize => PlayerHouse1.SIZE; + public override IPlayerHouse ReadPlayerHouse(byte[] data) => new PlayerHouse1(data); + + public override int PlayerRoomSize => PlayerHouse1.SIZE; + public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data); } } diff --git a/NHSE.Core/Save/Offsets/MainSaveOffsets11.cs b/NHSE.Core/Save/Offsets/MainSaveOffsets11.cs index 069ec41..efa8aeb 100644 --- a/NHSE.Core/Save/Offsets/MainSaveOffsets11.cs +++ b/NHSE.Core/Save/Offsets/MainSaveOffsets11.cs @@ -52,5 +52,10 @@ public class MainSaveOffsets11 : MainSaveOffsets public override int VillagerHouseSize => VillagerHouse1.SIZE; public override IVillagerHouse ReadVillagerHouse(byte[] data) => new VillagerHouse1(data); + + public override int PlayerHouseSize => PlayerHouse1.SIZE; + public override IPlayerHouse ReadPlayerHouse(byte[] data) => new PlayerHouse1(data); + public override int PlayerRoomSize => PlayerHouse1.SIZE; + public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data); } } diff --git a/NHSE.Core/Save/Offsets/MainSaveOffsets110.cs b/NHSE.Core/Save/Offsets/MainSaveOffsets110.cs index a30b28e..0c463aa 100644 --- a/NHSE.Core/Save/Offsets/MainSaveOffsets110.cs +++ b/NHSE.Core/Save/Offsets/MainSaveOffsets110.cs @@ -54,5 +54,10 @@ public class MainSaveOffsets110 : MainSaveOffsets public override int VillagerHouseSize => VillagerHouse1.SIZE; public override IVillagerHouse ReadVillagerHouse(byte[] data) => new VillagerHouse1(data); + + public override int PlayerHouseSize => PlayerHouse1.SIZE; + public override IPlayerHouse ReadPlayerHouse(byte[] data) => new PlayerHouse1(data); + public override int PlayerRoomSize => PlayerHouse1.SIZE; + public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data); } } diff --git a/NHSE.Core/Save/Offsets/MainSaveOffsets111.cs b/NHSE.Core/Save/Offsets/MainSaveOffsets111.cs index 03f1845..d765910 100644 --- a/NHSE.Core/Save/Offsets/MainSaveOffsets111.cs +++ b/NHSE.Core/Save/Offsets/MainSaveOffsets111.cs @@ -55,5 +55,10 @@ public class MainSaveOffsets111 : MainSaveOffsets public override int VillagerHouseSize => VillagerHouse1.SIZE; public override IVillagerHouse ReadVillagerHouse(byte[] data) => new VillagerHouse1(data); + + public override int PlayerHouseSize => PlayerHouse1.SIZE; + public override IPlayerHouse ReadPlayerHouse(byte[] data) => new PlayerHouse1(data); + public override int PlayerRoomSize => PlayerHouse1.SIZE; + public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data); } } diff --git a/NHSE.Core/Save/Offsets/MainSaveOffsets12.cs b/NHSE.Core/Save/Offsets/MainSaveOffsets12.cs index 6d0c529..9cc959d 100644 --- a/NHSE.Core/Save/Offsets/MainSaveOffsets12.cs +++ b/NHSE.Core/Save/Offsets/MainSaveOffsets12.cs @@ -52,5 +52,10 @@ public class MainSaveOffsets12 : MainSaveOffsets public override int VillagerHouseSize => VillagerHouse1.SIZE; public override IVillagerHouse ReadVillagerHouse(byte[] data) => new VillagerHouse1(data); + + public override int PlayerHouseSize => PlayerHouse1.SIZE; + public override IPlayerHouse ReadPlayerHouse(byte[] data) => new PlayerHouse1(data); + public override int PlayerRoomSize => PlayerHouse1.SIZE; + public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data); } } diff --git a/NHSE.Core/Save/Offsets/MainSaveOffsets13.cs b/NHSE.Core/Save/Offsets/MainSaveOffsets13.cs index 4c9bd06..e7a2231 100644 --- a/NHSE.Core/Save/Offsets/MainSaveOffsets13.cs +++ b/NHSE.Core/Save/Offsets/MainSaveOffsets13.cs @@ -52,5 +52,10 @@ public class MainSaveOffsets13 : MainSaveOffsets public override int VillagerHouseSize => VillagerHouse1.SIZE; public override IVillagerHouse ReadVillagerHouse(byte[] data) => new VillagerHouse1(data); + + public override int PlayerHouseSize => PlayerHouse1.SIZE; + public override IPlayerHouse ReadPlayerHouse(byte[] data) => new PlayerHouse1(data); + public override int PlayerRoomSize => PlayerHouse1.SIZE; + public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data); } } diff --git a/NHSE.Core/Save/Offsets/MainSaveOffsets14.cs b/NHSE.Core/Save/Offsets/MainSaveOffsets14.cs index 2f5bdf5..d05ad5c 100644 --- a/NHSE.Core/Save/Offsets/MainSaveOffsets14.cs +++ b/NHSE.Core/Save/Offsets/MainSaveOffsets14.cs @@ -52,5 +52,10 @@ public class MainSaveOffsets14 : MainSaveOffsets public override int VillagerHouseSize => VillagerHouse1.SIZE; public override IVillagerHouse ReadVillagerHouse(byte[] data) => new VillagerHouse1(data); + + public override int PlayerHouseSize => PlayerHouse1.SIZE; + public override IPlayerHouse ReadPlayerHouse(byte[] data) => new PlayerHouse1(data); + public override int PlayerRoomSize => PlayerHouse1.SIZE; + public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data); } } diff --git a/NHSE.Core/Save/Offsets/MainSaveOffsets15.cs b/NHSE.Core/Save/Offsets/MainSaveOffsets15.cs index 7eef30f..6225cce 100644 --- a/NHSE.Core/Save/Offsets/MainSaveOffsets15.cs +++ b/NHSE.Core/Save/Offsets/MainSaveOffsets15.cs @@ -52,5 +52,10 @@ public class MainSaveOffsets15 : MainSaveOffsets public override int VillagerHouseSize => VillagerHouse1.SIZE; public override IVillagerHouse ReadVillagerHouse(byte[] data) => new VillagerHouse1(data); + + public override int PlayerHouseSize => PlayerHouse1.SIZE; + public override IPlayerHouse ReadPlayerHouse(byte[] data) => new PlayerHouse1(data); + public override int PlayerRoomSize => PlayerHouse1.SIZE; + public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data); } } diff --git a/NHSE.Core/Save/Offsets/MainSaveOffsets16.cs b/NHSE.Core/Save/Offsets/MainSaveOffsets16.cs index f7f772d..492054b 100644 --- a/NHSE.Core/Save/Offsets/MainSaveOffsets16.cs +++ b/NHSE.Core/Save/Offsets/MainSaveOffsets16.cs @@ -52,5 +52,10 @@ public class MainSaveOffsets16 : MainSaveOffsets public override int VillagerHouseSize => VillagerHouse1.SIZE; public override IVillagerHouse ReadVillagerHouse(byte[] data) => new VillagerHouse1(data); + + public override int PlayerHouseSize => PlayerHouse1.SIZE; + public override IPlayerHouse ReadPlayerHouse(byte[] data) => new PlayerHouse1(data); + public override int PlayerRoomSize => PlayerHouse1.SIZE; + public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data); } } diff --git a/NHSE.Core/Save/Offsets/MainSaveOffsets17.cs b/NHSE.Core/Save/Offsets/MainSaveOffsets17.cs index 8921f46..8983478 100644 --- a/NHSE.Core/Save/Offsets/MainSaveOffsets17.cs +++ b/NHSE.Core/Save/Offsets/MainSaveOffsets17.cs @@ -52,5 +52,10 @@ public class MainSaveOffsets17 : MainSaveOffsets public override int VillagerHouseSize => VillagerHouse1.SIZE; public override IVillagerHouse ReadVillagerHouse(byte[] data) => new VillagerHouse1(data); + + public override int PlayerHouseSize => PlayerHouse1.SIZE; + public override IPlayerHouse ReadPlayerHouse(byte[] data) => new PlayerHouse1(data); + public override int PlayerRoomSize => PlayerHouse1.SIZE; + public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data); } } diff --git a/NHSE.Core/Save/Offsets/MainSaveOffsets18.cs b/NHSE.Core/Save/Offsets/MainSaveOffsets18.cs index 54a5f69..2e7636e 100644 --- a/NHSE.Core/Save/Offsets/MainSaveOffsets18.cs +++ b/NHSE.Core/Save/Offsets/MainSaveOffsets18.cs @@ -53,5 +53,10 @@ public class MainSaveOffsets18 : MainSaveOffsets public override int VillagerHouseSize => VillagerHouse1.SIZE; public override IVillagerHouse ReadVillagerHouse(byte[] data) => new VillagerHouse1(data); + + public override int PlayerHouseSize => PlayerHouse1.SIZE; + public override IPlayerHouse ReadPlayerHouse(byte[] data) => new PlayerHouse1(data); + public override int PlayerRoomSize => PlayerHouse1.SIZE; + public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data); } } diff --git a/NHSE.Core/Save/Offsets/MainSaveOffsets19.cs b/NHSE.Core/Save/Offsets/MainSaveOffsets19.cs index 3212d56..6d3ae3b 100644 --- a/NHSE.Core/Save/Offsets/MainSaveOffsets19.cs +++ b/NHSE.Core/Save/Offsets/MainSaveOffsets19.cs @@ -54,5 +54,10 @@ public class MainSaveOffsets19 : MainSaveOffsets public override int VillagerHouseSize => VillagerHouse1.SIZE; public override IVillagerHouse ReadVillagerHouse(byte[] data) => new VillagerHouse1(data); + + public override int PlayerHouseSize => PlayerHouse1.SIZE; + public override IPlayerHouse ReadPlayerHouse(byte[] data) => new PlayerHouse1(data); + public override int PlayerRoomSize => PlayerHouse1.SIZE; + public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data); } } diff --git a/NHSE.Core/Save/Offsets/MainSaveOffsets20.cs b/NHSE.Core/Save/Offsets/MainSaveOffsets20.cs index c3dab8e..2829614 100644 --- a/NHSE.Core/Save/Offsets/MainSaveOffsets20.cs +++ b/NHSE.Core/Save/Offsets/MainSaveOffsets20.cs @@ -55,5 +55,10 @@ public class MainSaveOffsets20 : MainSaveOffsets public override int VillagerHouseSize => VillagerHouse2.SIZE; public override IVillagerHouse ReadVillagerHouse(byte[] data) => new VillagerHouse2(data); + + public override int PlayerHouseSize => PlayerHouse2.SIZE; + public override IPlayerHouse ReadPlayerHouse(byte[] data) => new PlayerHouse2(data); + public override int PlayerRoomSize => PlayerHouse2.SIZE; + public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom2(data); } } diff --git a/NHSE.Core/Structures/Building/SoundAmbientKind.cs b/NHSE.Core/Structures/Building/SoundAmbientKind.cs new file mode 100644 index 0000000..0fbf9d7 --- /dev/null +++ b/NHSE.Core/Structures/Building/SoundAmbientKind.cs @@ -0,0 +1,33 @@ +namespace NHSE.Core +{ + /// + /// Ambient sound for a house. + /// + public enum SoundAmbientKind : byte + { + Silence = 0x0, + Rain = 0x1, + Sea = 0x2, + InWater = 0x3, + Wind = 0x4, + Plateau = 0x5, + Jungle = 0x6, + Crowd = 0x7, + Cheers = 0x8, + City = 0x9, + Train = 0xA, + Construction = 0xB, + Space = 0xC, + Echo = 0xD, + Storm = 0xE, + Cave = 0x10, + Earthquake = 0x11, + Squeak = 0x12, + Country = 0x14, + Factory = 0x15, + Cyber = 0x1A, + Healing = 0x1B, + Forest = 0x1C, + Duct = 0x1D, + } +} diff --git a/NHSE.Core/Structures/GameFileDumper.cs b/NHSE.Core/Structures/GameFileDumper.cs index c90c3c7..9a6938c 100644 --- a/NHSE.Core/Structures/GameFileDumper.cs +++ b/NHSE.Core/Structures/GameFileDumper.cs @@ -59,7 +59,7 @@ private static void Dump(string path, byte[] data, string name) /// /// /// Path to dump to - public static void DumpPlayerHouses(this IReadOnlyList houses, IReadOnlyList players, string path) + public static void DumpPlayerHouses(this IReadOnlyList houses, IReadOnlyList players, string path) { for (int i = 0; i < houses.Count; i++) { @@ -84,12 +84,12 @@ public static void DumpPlayerHouses(this HorizonSave sav, string path) } } - private static void Dump(this PlayerHouse h, string path, IVillagerOrigin p) => h.Dump(p.PlayerName, path); + private static void Dump(this IPlayerHouse h, string path, IVillagerOrigin p) => h.Dump(p.PlayerName, path); - private static void Dump(this PlayerHouse h, string player, string path) + private static void Dump(this IPlayerHouse h, string player, string path) { - var dest = Path.Combine(path, $"{player}.nhph"); - var data = h.Data; + var dest = Path.Combine(path, $"{player}.{h.Extension}"); + var data = h.Write(); File.WriteAllBytes(dest, data); } diff --git a/NHSE.Core/Structures/Map/Managers/RoomItemManager.cs b/NHSE.Core/Structures/Map/Managers/RoomItemManager.cs index f2551ed..c18c1d9 100644 --- a/NHSE.Core/Structures/Map/Managers/RoomItemManager.cs +++ b/NHSE.Core/Structures/Map/Managers/RoomItemManager.cs @@ -8,10 +8,10 @@ public class RoomItemManager { public readonly RoomItemLayer[] Layers; - public readonly PlayerRoom Room; + public readonly IPlayerRoom Room; private const int LayerCount = 8; - public RoomItemManager(PlayerRoom room) + public RoomItemManager(IPlayerRoom room) { Layers = room.GetItemLayers(); Room = room; diff --git a/NHSE.Core/Structures/Villager/IPlayerHouse.cs b/NHSE.Core/Structures/Villager/IPlayerHouse.cs new file mode 100644 index 0000000..ef1c898 --- /dev/null +++ b/NHSE.Core/Structures/Villager/IPlayerHouse.cs @@ -0,0 +1,16 @@ +namespace NHSE.Core +{ + public interface IPlayerHouse : IHouseInfo + { + /// Returns the inner data buffer of the object, flushing any pending changes. + byte[] Write(); + + sbyte NPC1 { get; set; } + sbyte NPC2 { get; set; } + string Extension { get; } + short[] GetEventFlags(); + void SetEventFlags(short[] value); + IPlayerRoom GetRoom(int v); + void SetRoom(int roomIndex, IPlayerRoom room); + } +} diff --git a/NHSE.Core/Structures/Villager/IPlayerRoom.cs b/NHSE.Core/Structures/Villager/IPlayerRoom.cs new file mode 100644 index 0000000..4e85fb2 --- /dev/null +++ b/NHSE.Core/Structures/Villager/IPlayerRoom.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace NHSE.Core +{ + public interface IPlayerRoom + { + /// Returns the inner data buffer of the object, flushing any pending changes. + byte[] Write(); + + string Extension { get; } + RoomItemLayer[] GetItemLayers(); + void SetItemLayers(IReadOnlyList value); + IPlayerRoom Upgrade(); + } +} diff --git a/NHSE.Core/Structures/Villager/PlayerHouse.cs b/NHSE.Core/Structures/Villager/PlayerHouse1.cs similarity index 75% rename from NHSE.Core/Structures/Villager/PlayerHouse.cs rename to NHSE.Core/Structures/Villager/PlayerHouse1.cs index de8d814..761eb76 100644 --- a/NHSE.Core/Structures/Villager/PlayerHouse.cs +++ b/NHSE.Core/Structures/Villager/PlayerHouse1.cs @@ -2,12 +2,15 @@ namespace NHSE.Core { - public class PlayerHouse : IHouseInfo + public class PlayerHouse1 : IPlayerHouse { public const int SIZE = 0x26400; + public virtual string Extension => "nhph"; public readonly byte[] Data; - public PlayerHouse(byte[] data) => Data = data; + public PlayerHouse1(byte[] data) => Data = data; + + public byte[] Write() => Data; 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); } @@ -39,29 +42,29 @@ public short[] GetEventFlags() 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 const int RoomStart = 0x120; - public PlayerRoom GetRoom(int roomIndex) + public IPlayerRoom 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); + var data = Data.Slice(RoomStart + (roomIndex * PlayerRoom1.SIZE), PlayerRoom1.SIZE); + return new PlayerRoom1(data); } - public void SetRoom(int roomIndex, PlayerRoom room) + public void SetRoom(int roomIndex, IPlayerRoom room) { if ((uint)roomIndex >= MaxRoom) throw new ArgumentOutOfRangeException(nameof(roomIndex)); - room.Data.CopyTo(Data, RoomStart + (roomIndex * PlayerRoom.SIZE)); + room.Write().CopyTo(Data, RoomStart + (roomIndex * PlayerRoom1.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 + // 2 bytes padding public Item DoorDecoItemName { @@ -84,5 +87,15 @@ public Item OrderPostItemName } // cockroach @ 0x263f0 -- meh + + public PlayerHouse2 Upgrade() + { + var data = new byte[PlayerHouse2.SIZE]; + Data.Slice(0x0, 0x120).CopyTo(data, 0); // HouseLevel -> EventFlag + for (int i = 0; i < MaxRoom; i++) + GetRoom(i).Upgrade().Write().CopyTo(data, 0x120 + i * PlayerRoom2.SIZE); // RoomList + Data.Slice(0x263D0, 0x30).CopyTo(data, 0x289F8); // PlayerList -> Cockroach + return new PlayerHouse2(data); + } } } diff --git a/NHSE.Core/Structures/Villager/PlayerHouse2.cs b/NHSE.Core/Structures/Villager/PlayerHouse2.cs new file mode 100644 index 0000000..9a58942 --- /dev/null +++ b/NHSE.Core/Structures/Villager/PlayerHouse2.cs @@ -0,0 +1,66 @@ +using System; + +namespace NHSE.Core +{ + public class PlayerHouse2 : PlayerHouse1 + { + public new const int SIZE = 0x28A28; + public override string Extension => "nhph2"; + + public PlayerHouse2(byte[] data) : base(data) { } + + public new PlayerRoom2 GetRoom(int roomIndex) + { + if ((uint)roomIndex >= MaxRoom) + throw new ArgumentOutOfRangeException(nameof(roomIndex)); + + var data = Data.Slice(RoomStart + (roomIndex * PlayerRoom2.SIZE), PlayerRoom2.SIZE); + return new PlayerRoom2(data); + } + + public void SetRoom(int roomIndex, PlayerRoom2 room) + { + if ((uint)roomIndex >= MaxRoom) + throw new ArgumentOutOfRangeException(nameof(roomIndex)); + + room.Data.CopyTo(Data, RoomStart + (roomIndex * PlayerRoom2.SIZE)); + } + + public new sbyte NPC1 { get => (sbyte)Data[0x289F8]; set => Data[0x289F8] = (byte)value; } + public new sbyte NPC2 { get => (sbyte)Data[0x289F9]; set => Data[0x289F9] = (byte)value; } + + // 2 bytes padding + + public new Item DoorDecoItemName + { + get => Data.Slice(0x289FC, 8).ToClass(); + set => value.ToBytesClass().CopyTo(Data, 0x289FC); + } + + public new bool PlayerHouseFlag { get => Data[0x28A04] != 0; set => Data[0x28A04] = (byte)(value ? 1 : 0); } + + public new Item PostItemName + { + get => Data.Slice(0x28A08, 8).ToClass(); + set => value.ToBytesClass().CopyTo(Data, 0x28A08); + } + + public new Item OrderPostItemName + { + get => Data.Slice(0x28A10, 8).ToClass(); + set => value.ToBytesClass().CopyTo(Data, 0x28A10); + } + + // cockroach @ 0x28A18 -- meh + + public PlayerHouse1 Downgrade() + { + var data = new byte[PlayerHouse1.SIZE]; + Data.Slice(0x0, 0x120).CopyTo(data, 0); // HouseLevel -> EventFlag + for (int i = 0; i < MaxRoom; i++) + GetRoom(i).Downgrade().Data.CopyTo(data, 0x120 + i * PlayerRoom2.SIZE); // RoomList + Data.Slice(0x289F8, 0x30).CopyTo(data, 0x263D0); // PlayerList -> Cockroach + return new PlayerHouse1(data); + } + } +} diff --git a/NHSE.Core/Structures/Villager/PlayerHouseConverter.cs b/NHSE.Core/Structures/Villager/PlayerHouseConverter.cs new file mode 100644 index 0000000..e0eca1e --- /dev/null +++ b/NHSE.Core/Structures/Villager/PlayerHouseConverter.cs @@ -0,0 +1,73 @@ +namespace NHSE.Core +{ + /// + /// Converts Player House objects to different revisions. + /// + public static class PlayerHouseConverter + { + /// + /// Checks to see if the matches any of the House object sizes. + /// + /// Size of the byte array that might represent a House object. + /// True if it matches any size. + public static bool IsHouse(int size) + { + return size == PlayerHouse1.SIZE || size == PlayerHouse2.SIZE; + } + + /// + /// Checks to see if the input can be converted to the size. + /// + /// Current house file size + /// Expected house file size + /// True if can be converted, false if no conversion available. + public static bool IsCompatible(int size, int expect) + { + return expect switch + { + // Can convert to any format + PlayerHouse1.SIZE or PlayerHouse2.SIZE => IsHouse(size), + // No conversion available + _ => false, + }; + } + + /// + /// Converts the House data format to another format. + /// + /// + /// Before calling this method, check that a conversion method exists via and that the length of the is not the same as what you . + /// If the sizes are the same, it will return the input . + /// If no conversion path exists, it will return the input . + /// + /// Current format + /// Target size + /// Converted data + public static byte[] GetCompatible(byte[] data, int expect) + { + if (data.Length == expect) + return data; + + return expect switch + { + PlayerHouse1.SIZE when data.Length == PlayerHouse2.SIZE => Convert21(data), + PlayerHouse2.SIZE when data.Length == PlayerHouse1.SIZE => Convert12(data), + _ => data, + }; + } + + /// + /// Converts a object byte array to a + /// + /// object byte array + /// object byte array + private static byte[] Convert12(byte[] h1) => new PlayerHouse1(h1).Upgrade().Data; + + /// + /// Converts a object byte array to a + /// + /// object byte array + /// object byte array + private static byte[] Convert21(byte[] h2) => new PlayerHouse2(h2).Downgrade().Data; + } +} diff --git a/NHSE.Core/Structures/Villager/PlayerRoom.cs b/NHSE.Core/Structures/Villager/PlayerRoom1.cs similarity index 81% rename from NHSE.Core/Structures/Villager/PlayerRoom.cs rename to NHSE.Core/Structures/Villager/PlayerRoom1.cs index c5b1359..3fe890c 100644 --- a/NHSE.Core/Structures/Villager/PlayerRoom.cs +++ b/NHSE.Core/Structures/Villager/PlayerRoom1.cs @@ -2,12 +2,15 @@ namespace NHSE.Core { - public class PlayerRoom + public class PlayerRoom1 : IPlayerRoom { public const int SIZE = 0x65C8; + public virtual string Extension => "nhpr"; public readonly byte[] Data; - public PlayerRoom(byte[] data) => Data = data; + public PlayerRoom1(byte[] data) => Data = data; + + public byte[] Write() => Data; /* s_d8bc748b ItemLayerList[8]; // @0x0 size 0xc80, align 4 @@ -34,5 +37,12 @@ public GSaveRoomFloorWall RoomFloorWall get => Data.Slice(0x65A4, GSaveRoomFloorWall.SIZE).ToStructure(); set => value.ToBytes().CopyTo(Data, 0x65A4); } + + public IPlayerRoom Upgrade() + { + var data = new byte[PlayerRoom2.SIZE]; + Data.CopyTo(data, 0); + return new PlayerRoom2(data); + } } } diff --git a/NHSE.Core/Structures/Villager/PlayerRoom2.cs b/NHSE.Core/Structures/Villager/PlayerRoom2.cs new file mode 100644 index 0000000..255543b --- /dev/null +++ b/NHSE.Core/Structures/Villager/PlayerRoom2.cs @@ -0,0 +1,91 @@ +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; + +namespace NHSE.Core +{ + public class PlayerRoom2 : PlayerRoom1 + { + public new const int SIZE = 0x6C24; + public new virtual string Extension => "nhpr2"; + + public PlayerRoom2(byte[] data) : base(data) { } + + /* + s_665e9093 ExtraEffectLayerList[2]; // @0x65c8 size 0x320, align 2 + GSaveMusicBoxInfo MusicBoxInfo; // @0x6c08 size 0x4, align 2 + u8 SoundAmbient; // @0x6c0c size 0x1, align 1 + u8 gap_6c0d[3]; + s_e13a81f4 _cfb139b9; // @0x6c10 size 0x14, align 4 + */ + + public s_665e9093[] ExtraEffectLayerList + { + get + { + var result = new s_665e9093[2]; + for (int i = 0; i < result.Length; i++) + { + var slice = Data.Slice(0x65C8 + i * s_665e9093.SIZE, s_665e9093.SIZE); + result[i] = slice.ToStructure(); + } + return result; + } + + set => value.ToBytesClass().CopyTo(Data, 0x65C8); + } + + public GSaveMusicBoxInfo MusicBoxInfo + { + get => Data.Slice(0x6C08, GSaveMusicBoxInfo.SIZE).ToStructure(); + set => value.ToBytes().CopyTo(Data, 0x6C08); + } + + public SoundAmbientKind SoundAmbientUniqueID { get => (SoundAmbientKind)Data[0x6C0C]; set => Data[0x6C0C]= (byte)value; } + + // 3 bytes padding + + public s_e13a81f4 _cfb139b9 + { + get => Data.Slice(0x6C10, s_e13a81f4.SIZE).ToStructure(); + set => value.ToBytes().CopyTo(Data, 0x6C10); + } + + public PlayerRoom1 Downgrade() + { + var result = new byte[PlayerRoom1.SIZE]; + Data.CopyTo(result, 0); + return new PlayerRoom1(result); + } + } + +#pragma warning disable CS8618, CA1815, CA1819, IDE1006, RCS1170 + [StructLayout(LayoutKind.Sequential, Pack = 2, Size = SIZE)] + [TypeConverter(typeof(ValueTypeTypeConverter))] + public struct s_665e9093 + { + public const int SIZE = 0x320; /* 0x320 big, align 2 */ + + [field: MarshalAs(UnmanagedType.LPArray, SizeConst = 400)] + public GSaveItemExtraEffect[] ItemExtraEffectBuffer { get; set; } // @0x0 size 0x2, align 2 + } + + [StructLayout(LayoutKind.Sequential, Pack = 2, Size = SIZE)] + [TypeConverter(typeof(ValueTypeTypeConverter))] + public struct GSaveItemExtraEffect + { + public const int SIZE = 0x2; /* 0x2 big, align 2 */ + + public ushort InnerData { get; set; } // @0x0 size 0x2, align 2 + } + + [StructLayout(LayoutKind.Sequential, Pack = 2, Size = SIZE)] + [TypeConverter(typeof(ValueTypeTypeConverter))] + public struct GSaveMusicBoxInfo + { + public const int SIZE = 0x4; /* 0x4 big, align 2 */ + + public ushort PlayingAudioMusicID { get; set; } // @0x0 size 0x2, align 2 + public byte _749b78c2 { get; set; } // @0x2 size 0x1, align 1 + } +} diff --git a/NHSE.Core/Structures/Villager/PlayerRoomConverter.cs b/NHSE.Core/Structures/Villager/PlayerRoomConverter.cs new file mode 100644 index 0000000..bfffa17 --- /dev/null +++ b/NHSE.Core/Structures/Villager/PlayerRoomConverter.cs @@ -0,0 +1,73 @@ +namespace NHSE.Core +{ + /// + /// Converts Player Room objects to different revisions. + /// + public static class PlayerRoomConverter + { + /// + /// Checks to see if the matches any of the Room object sizes. + /// + /// Size of the byte array that might represent a Room object. + /// True if it matches any size. + public static bool IsRoom(int size) + { + return size == PlayerRoom1.SIZE || size == PlayerRoom2.SIZE; + } + + /// + /// Checks to see if the input can be converted to the size. + /// + /// Current room file size + /// Expected room file size + /// True if can be converted, false if no conversion available. + public static bool IsCompatible(int size, int expect) + { + return expect switch + { + // Can convert to any format + PlayerRoom1.SIZE or PlayerRoom2.SIZE => IsRoom(size), + // No conversion available + _ => false, + }; + } + + /// + /// Converts the Room data format to another format. + /// + /// + /// Before calling this method, check that a conversion method exists via and that the length of the is not the same as what you . + /// If the sizes are the same, it will return the input . + /// If no conversion path exists, it will return the input . + /// + /// Current format + /// Target size + /// Converted data + public static byte[] GetCompatible(byte[] data, int expect) + { + if (data.Length == expect) + return data; + + return expect switch + { + PlayerRoom1.SIZE when data.Length == PlayerRoom2.SIZE => Convert21(data), + PlayerRoom2.SIZE when data.Length == PlayerRoom1.SIZE => Convert12(data), + _ => data, + }; + } + + /// + /// Converts a object byte array to a + /// + /// object byte array + /// object byte array + private static byte[] Convert12(byte[] h1) => new PlayerRoom1(h1).Upgrade().Write(); + + /// + /// Converts a object byte array to a + /// + /// object byte array + /// object byte array + private static byte[] Convert21(byte[] h2) => new PlayerRoom2(h2).Downgrade().Write(); + } +} diff --git a/NHSE.WinForms/Editor.cs b/NHSE.WinForms/Editor.cs index e0b814a..fd844f3 100644 --- a/NHSE.WinForms/Editor.cs +++ b/NHSE.WinForms/Editor.cs @@ -470,7 +470,7 @@ private void B_EditDesignsTailor_Click(object sender, EventArgs e) private void B_EditPlayerHouses_Click(object sender, EventArgs e) { var houses = SAV.Main.GetPlayerHouses(); - using var editor = new PlayerHouseEditor(houses, SAV.Players, PlayerIndex); + using var editor = new PlayerHouseEditor(houses, SAV.Players, SAV.Main, PlayerIndex); if (editor.ShowDialog() == DialogResult.OK) SAV.Main.SetPlayerHouses(houses); } diff --git a/NHSE.WinForms/Subforms/Map/MiscDumpHelper.cs b/NHSE.WinForms/Subforms/Map/MiscDumpHelper.cs index 15619ad..7627bd7 100644 --- a/NHSE.WinForms/Subforms/Map/MiscDumpHelper.cs +++ b/NHSE.WinForms/Subforms/Map/MiscDumpHelper.cs @@ -96,7 +96,7 @@ public static bool LoadMuseum(Museum museum) return true; } - public static void DumpHouse(IReadOnlyList players, IReadOnlyList houses, int index, bool dumpAll) + public static void DumpHouse(IReadOnlyList players, IReadOnlyList houses, int index, bool dumpAll) { if (dumpAll) DumpAllPlayerHouses(houses, players); @@ -104,23 +104,25 @@ public static void DumpHouse(IReadOnlyList players, IReadOnlyList players, IReadOnlyList houses, int index) + private static void DumpPlayerHouse(IReadOnlyList players, IReadOnlyList houses, int index) { var house = houses[index]; var name = PlayerHouseEditor.GetHouseSummary(players, house, index); using var sfd = new SaveFileDialog { - Filter = "New Horizons Player House (*.nhph)|*.nhph|All files (*.*)|*.*", - FileName = $"{name}.nhph", + Filter = "New Horizons Player House (*.nhph)|*.nhph|" + + "New Horizons Player House (*.nhph2)|*.nhph2|" + + "All files (*.*)|*.*", + FileName = $"{name}.{house.Extension}", }; if (sfd.ShowDialog() != DialogResult.OK) return; - var data = house.Data; + var data = house.Write(); File.WriteAllBytes(sfd.FileName, data); } - private static void DumpAllPlayerHouses(IReadOnlyList houses, IReadOnlyList players) + private static void DumpAllPlayerHouses(IReadOnlyList houses, IReadOnlyList players) { using var fbd = new FolderBrowserDialog(); if (fbd.ShowDialog() != DialogResult.OK) @@ -132,69 +134,90 @@ private static void DumpAllPlayerHouses(IReadOnlyList houses, IRead houses.DumpPlayerHouses(players, fbd.SelectedPath); } - public static bool LoadHouse(IReadOnlyList players, PlayerHouse[] houses, int index) + public static bool LoadHouse(MainSaveOffsets offsets, IReadOnlyList players, IPlayerHouse[] houses, int index) { + var h = houses[index]; var name = PlayerHouseEditor.GetHouseSummary(players, houses[index], index); using var ofd = new OpenFileDialog { - Filter = "New Horizons Player House (*.nhph)|*.nhph|All files (*.*)|*.*", - FileName = $"{name}.nhph", + Filter = "New Horizons Player House (*.nhph)|*.nhph|" + + "New Horizons Player House (*.nhph2)|*.nhph2|" + + "All files (*.*)|*.*", + FileName = $"{name}.{h.Extension}", }; if (ofd.ShowDialog() != DialogResult.OK) return false; - var file = ofd.FileName; - var fi = new FileInfo(file); - const int expectLength = PlayerHouse.SIZE; - if (fi.Length != expectLength) + var path = ofd.FileName; + var expectLength = offsets.PlayerHouseSize; + var fi = new FileInfo(path); + if (!VillagerHouseConverter.IsCompatible((int)fi.Length, expectLength)) { - WinFormsUtil.Error(MessageStrings.MsgCanceling, string.Format(MessageStrings.MsgDataSizeMismatchImport, fi.Length, expectLength)); + WinFormsUtil.Error(string.Format(MessageStrings.MsgDataSizeMismatchImport, fi.Length, expectLength), path); return false; } - var data = File.ReadAllBytes(file); - var h = new PlayerHouse(data); + var data = File.ReadAllBytes(ofd.FileName); + data = PlayerHouseConverter.GetCompatible(data, expectLength); + if (fi.Length != expectLength) + { + WinFormsUtil.Error(MessageStrings.MsgCanceling, string.Format(MessageStrings.MsgDataSizeMismatchImport, fi.Length, expectLength), path); + return false; + } + + h = offsets.ReadPlayerHouse(data); var current = houses[index]; h.NPC1 = current.NPC1; houses[index] = h; return true; } - public static void DumpRoom(PlayerRoom room, int index) + public static void DumpRoom(IPlayerRoom room, int index) { using var sfd = new SaveFileDialog { - Filter = "New Horizons Player House Room (*.nhpr)|*.nhpr|All files (*.*)|*.*", - FileName = $"Room {index + 1}.nhpr", + Filter = "New Horizons Player House Room (*.nhpr)|*.nhpr|" + + "New Horizons Player House Room (*.nhpr2)|*.nhpr2|" + + "All files (*.*)|*.*", + FileName = $"Room {index + 1}.{room.Extension}", }; if (sfd.ShowDialog() != DialogResult.OK) return; - var data = room.Data; + var data = room.Write(); File.WriteAllBytes(sfd.FileName, data); } - public static bool LoadRoom(PlayerRoom room, int index) + public static bool LoadRoom(MainSaveOffsets offsets, IPlayerRoom room, int index) { using var ofd = new OpenFileDialog { - Filter = "New Horizons Player House Room (*.nhpr)|*.nhpr|All files (*.*)|*.*", - FileName = $"Room {index + 1}.nhpr", + Filter = "New Horizons Player House Room (*.nhpr)|*.nhpr|" + + "New Horizons Player House Room (*.nhpr2)|*.nhpr2|" + + "All files (*.*)|*.*", + FileName = $"Room {index + 1}.{room.Extension}", }; if (ofd.ShowDialog() != DialogResult.OK) return false; - var file = ofd.FileName; - var fi = new FileInfo(file); - const int expectLength = PlayerRoom.SIZE; - if (fi.Length != expectLength) + var path = ofd.FileName; + var expectLength = offsets.PlayerRoomSize; + var fi = new FileInfo(path); + if (!PlayerRoomConverter.IsCompatible((int)fi.Length, expectLength)) { - WinFormsUtil.Error(MessageStrings.MsgCanceling, string.Format(MessageStrings.MsgDataSizeMismatchImport, fi.Length, expectLength)); + WinFormsUtil.Error(string.Format(MessageStrings.MsgDataSizeMismatchImport, fi.Length, expectLength), path); return false; } - var data = File.ReadAllBytes(file); - data.CopyTo(room.Data, 0); + var data = File.ReadAllBytes(ofd.FileName); + data = PlayerRoomConverter.GetCompatible(data, offsets.PlayerRoomSize); + if (fi.Length != expectLength) + { + WinFormsUtil.Error(MessageStrings.MsgCanceling, string.Format(MessageStrings.MsgDataSizeMismatchImport, fi.Length, expectLength), path); + return false; + } + + room = offsets.ReadPlayerRoom(data); return true; } diff --git a/NHSE.WinForms/Subforms/Map/PlayerHouseEditor.cs b/NHSE.WinForms/Subforms/Map/PlayerHouseEditor.cs index 94f8f0f..0606174 100644 --- a/NHSE.WinForms/Subforms/Map/PlayerHouseEditor.cs +++ b/NHSE.WinForms/Subforms/Map/PlayerHouseEditor.cs @@ -9,7 +9,8 @@ namespace NHSE.WinForms { public partial class PlayerHouseEditor : Form { - private readonly PlayerHouse[] Houses; + private readonly MainSave SAV; + private readonly IPlayerHouse[] Houses; private readonly IReadOnlyList Players; private RoomItemManager Manager; private const int scale = 24; @@ -17,10 +18,11 @@ public partial class PlayerHouseEditor : Form private int Index = -1; private int RoomIndex = -1; - public PlayerHouseEditor(PlayerHouse[] houses, IReadOnlyList players, int index) + public PlayerHouseEditor(IPlayerHouse[] houses, IReadOnlyList players, MainSave sav, int index) { InitializeComponent(); this.TranslateInterface(GameInfo.CurrentLanguage); + SAV = sav; Houses = houses; Players = players; Manager = new RoomItemManager(houses[0].GetRoom(0)); @@ -73,7 +75,7 @@ private void PG_Item_PropertyValueChanged(object s, PropertyValueChangedEventArg LB_Items.Items[Index] = GetHouseSummary(Players, Houses[Index], Index); } - public static string GetHouseSummary(IReadOnlyList players, PlayerHouse house, int index) + public static string GetHouseSummary(IReadOnlyList players, IPlayerHouse house, int index) { var houseName = index >= players.Count ? $"House {index}" : $"{players[index].Personal.PlayerName}'s House"; return $"{houseName} (lv {house.HouseLevel})"; @@ -86,7 +88,7 @@ private void B_DumpHouse_Click(object sender, EventArgs e) private void B_LoadHouse_Click(object sender, EventArgs e) { - if (!MiscDumpHelper.LoadHouse(Players, Houses, Index)) + if (!MiscDumpHelper.LoadHouse(SAV.Offsets, Players, Houses, Index)) return; RoomIndex = -1; @@ -141,14 +143,14 @@ private static void GetCoordinates(MouseEventArgs e, out int x, out int y) y = e.Y / scale; } - private void ChangeRoom(PlayerHouse house) + private void ChangeRoom(IPlayerHouse house) { RoomIndex = (int) NUD_Room.Value - 1; ReloadManager(house); DrawLayer(); } - private void ReloadManager(PlayerHouse house) + private void ReloadManager(IPlayerHouse house) { var unsupported = Manager.GetUnsupportedTiles(); if (unsupported.Count != 0) @@ -300,7 +302,7 @@ private void DeleteTile(Item tile, int x, int y) private void B_LoadRoom_Click(object sender, EventArgs e) { var room = Manager.Room; - MiscDumpHelper.LoadRoom(room, RoomIndex); + MiscDumpHelper.LoadRoom(SAV.Offsets, room, RoomIndex); var house = Houses[Index]; house.SetRoom(RoomIndex, room);