using System; using System.Collections.Generic; using System.Runtime.InteropServices; using static System.Buffers.Binary.BinaryPrimitives; namespace NHSE.Core; /// /// main.dat /// public sealed class MainSave : EncryptedFilePair { public readonly MainSaveOffsets Offsets; public MainSave(ISaveFileProvider provider) : base(provider, "main") => Offsets = MainSaveOffsets.GetOffsets(Info); public Hemisphere Hemisphere { get => (Hemisphere)Data[Offsets.WeatherArea]; set => Data[Offsets.WeatherArea] = (byte)value; } public Hemisphere TourHemisphere { get => (Hemisphere)Data[Offsets.TourHemisphere]; set => Data[Offsets.TourHemisphere] = (byte)value; } public AirportColor AirportThemeColor { get => (AirportColor)Data[Offsets.AirportThemeColor]; set => Data[Offsets.AirportThemeColor] = (byte)value; } public uint WeatherSeed { get => ReadUInt32LittleEndian(Data[Offsets.WeatherRandSeed..]); set => WriteUInt32LittleEndian(Data[Offsets.WeatherRandSeed..], value); } public uint TourWeatherSeed { get => ReadUInt32LittleEndian(Data[Offsets.TourWeatherRandSeed..]); set => WriteUInt32LittleEndian(Data[Offsets.TourWeatherRandSeed..], value); } public byte CampsiteStatus { get => Data[Offsets.GSaveCampSite]; set => Data[Offsets.GSaveCampSite] = value; } public byte CampsiteVillagerVariantID { get => Data[Offsets.GSaveNpcCamp]; set => Data[Offsets.GSaveNpcCamp] = value; } public byte CampsiteVillagerSpeciesID { get => Data[Offsets.GSaveNpcCamp+1]; set => Data[Offsets.GSaveNpcCamp+1] = value; } public GSaveDate CampTimestamp { get => Data.ToStructure(Offsets.CampLastVisitTime, GSaveDate.SIZE); set => value.ToBytes().CopyTo(Data[Offsets.CampLastVisitTime..]); } public IVillager GetVillager(int index) => Offsets.ReadVillager(Data, index); public void SetVillager(IVillager value, int index) => Offsets.WriteVillager(value, Data, index); public IVillagerHouse GetVillagerHouse(int index) => Offsets.ReadVillagerHouse(Data, index); public void SetVillagerHouse(IVillagerHouse value, int index) => Offsets.WriteVillagerHouse(value, Data, index); public IVillager[] GetVillagers() { var villagers = new IVillager[MainSaveOffsets.VillagerCount]; for (int i = 0; i < villagers.Length; i++) villagers[i] = GetVillager(i); return villagers; } public void SetVillagers(IReadOnlyList villagers) { for (int i = 0; i < villagers.Count; i++) SetVillager(villagers[i], i); } public IVillagerHouse[] GetVillagerHouses() { var villagers = new IVillagerHouse[MainSaveOffsets.VillagerCount]; for (int i = 0; i < villagers.Length; i++) villagers[i] = GetVillagerHouse(i); return villagers; } public void SetVillagerHouses(IReadOnlyList villagers) { for (int i = 0; i < villagers.Count; i++) SetVillagerHouse(villagers[i], i); } public DesignPattern GetDesign(int index) => Offsets.ReadPattern(Data, index); public void SetDesign(DesignPattern value, int index) => Offsets.WritePattern(value, Data, index); public DesignPatternPRO GetDesignPRO(int index) => Offsets.ReadPatternPRO(Data, index); public void SetDesignPRO(DesignPatternPRO value, int index) => Offsets.WritePatternPRO(value, Data, index); public IReadOnlyList RecycleBin { get => Item.GetArray(Data.Slice(Offsets.LostItemBox, MainSaveOffsets.RecycleBinCount * Item.SIZE)); set => Item.SetArray(value).CopyTo(Data[Offsets.LostItemBox..]); } public IReadOnlyList Buildings { get => Building.GetArray(Data.Slice(Offsets.MainFieldStructure, MainSaveOffsets.BuildingCount * Building.SIZE)); set => Building.SetArray(value).CopyTo(Data[Offsets.MainFieldStructure..]); } public IPlayerHouse GetPlayerHouse(int index) => Offsets.ReadPlayerHouse(Data, index); public void SetPlayerHouse(IPlayerHouse value, int index) => Offsets.WritePlayerHouse(value, Data, index); public IPlayerHouse[] GetPlayerHouses() { var players = new IPlayerHouse[MainSaveOffsets.PlayerCount]; for (int i = 0; i < players.Length; i++) players[i] = GetPlayerHouse(i); return players; } public void SetPlayerHouses(IReadOnlyList houses) { for (int i = 0; i < houses.Count; i++) SetPlayerHouse(houses[i], i); } public DesignPattern[] GetDesigns() { var result = new DesignPattern[Offsets.PatternCount]; for (int i = 0; i value) { var count = Math.Min(Offsets.PatternCount, value.Count); for (int i = 0; i < count; i++) SetDesign(value[i], i); } public DesignPatternPRO[] GetDesignsPRO() { var result = new DesignPatternPRO[Offsets.PatternCount]; for (int i = 0; i < result.Length; i++) result[i] = GetDesignPRO(i); return result; } public void SetDesignsPRO(IReadOnlyList value) { var count = Math.Min(Offsets.PatternCount, value.Count); for (int i = 0; i < count; i++) SetDesignPRO(value[i], i); } public DesignPattern FlagMyDesign { get => MainSaveOffsets.ReadPatternAtOffset(Data, Offsets.PatternFlag); set => value.Data.CopyTo(Data[Offsets.PatternFlag..]); } public DesignPatternPRO[] GetDesignsTailor() { var result = new DesignPatternPRO[MainSaveOffsets.PatternTailorCount]; for (int i = 0; i < result.Length; i++) result[i] = MainSaveOffsets.ReadPatternPROAtOffset(Data, Offsets.PatternTailor + (i * DesignPatternPRO.SIZE)); return result; } public void SetDesignsTailor(IReadOnlyList value) { var count = Math.Min(Offsets.PatternCount, value.Count); for (int i = 0; i < count; i++) value[i].Data.CopyTo(Data[(Offsets.PatternTailor + (i * DesignPatternPRO.SIZE))..]); } private const int EventFlagsSaveCount = 0x400; public Span EventFlagLand => MemoryMarshal.Cast(Data.Slice(Offsets.EventFlagLand, sizeof(short) * EventFlagsSaveCount)); public TurnipStonk Turnips { get => Data.Slice(Offsets.ShopKabu, TurnipStonk.SIZE).ToArray().ToClass(); set => value.ToBytesClass().CopyTo(Data[Offsets.ShopKabu..]); } public Museum Museum { get => new(Data.Slice(Offsets.Museum, Museum.SIZE).ToArray()); set => value.Data.CopyTo(Data[Offsets.Museum..]); } // Acre Layout/Selection of which baselayer is selected for an acre. private const int AcreWidth = 7 + (2 * 1); // 1 on each side cannot be traversed private const int AcreHeight = 6 + (2 * 1); // 1 on each side cannot be traversed private const int AcreMax = AcreWidth * AcreHeight; private const int AcreSizeAll = AcreMax * 2; public ushort GetAcre(int index) { if ((uint)index > AcreMax) throw new ArgumentOutOfRangeException(nameof(index)); return ReadUInt16LittleEndian(Data[(Offsets.OutsideField + (index * 2))..]); } public void SetAcre(int index, ushort value) { if ((uint)index > AcreMax) throw new ArgumentOutOfRangeException(nameof(index)); WriteUInt16LittleEndian(Data[(Offsets.OutsideField + (index * 2))..], value); } public byte[] GetAcreBytes() => Data.Slice(Offsets.OutsideField, AcreSizeAll).ToArray(); public void SetAcreBytes(ReadOnlySpan data) { if (data.Length != AcreSizeAll) throw new ArgumentOutOfRangeException(nameof(data.Length)); data.CopyTo(Data[Offsets.OutsideField..]); } #pragma warning disable CA1822 // Mark members as static public byte FieldItemAcreWidth => Offsets.FieldItemAcreWidth; // 3.0.0 updated from 7 => 9 // ReSharper disable once MemberCanBeMadeStatic.Global public byte FieldItemAcreHeight => 6; // always 6 private int FieldItemAcreCount => FieldItemAcreWidth * FieldItemAcreHeight; #pragma warning restore CA1822 // Mark members as static private const int TotalTerrainTileCount = LayerTerrain.TilesPerAcreDim * LayerTerrain.TilesPerAcreDim * (7 * 6); private int TotalFieldItemTileCount => LayerFieldItem.TilesPerAcreDim * LayerFieldItem.TilesPerAcreDim * FieldItemAcreCount; public TerrainTile[] GetTerrainTiles() => TerrainTile.GetArray(Data.Slice(Offsets.LandMakingMap, TotalTerrainTileCount * TerrainTile.SIZE)); public void SetTerrainTiles(IReadOnlyList array) => TerrainTile.SetArray(array).CopyTo(Data[Offsets.LandMakingMap..]); public const ushort MapDesignNone = 0xF800; public Memory MapDesignTileData(int w, int h) => Raw.Slice(Offsets.MyDesignMap, (w * 16) * (h * 16) * sizeof(ushort)); public ushort[] GetMapDesignTiles(int w, int h) => MemoryMarshal.Cast(MapDesignTileData(w, h).Span).ToArray(); public void SetMapDesignTiles(ReadOnlySpan value, int w, int h) => MemoryMarshal.Cast(value).CopyTo(MapDesignTileData(w, h).Span); public void ClearDesignTiles(int w, int h) { var tiles = GetMapDesignTiles(w, h); tiles.AsSpan().Fill(MapDesignNone); SetMapDesignTiles(tiles, w, h); } private int FieldItemLayerSize => TotalFieldItemTileCount * Item.SIZE; private int FieldItemFlagSize => TotalFieldItemTileCount / sizeof(byte); // bitflags private int FieldItemLayer0 => Offsets.FieldItem; private int FieldItemLayer1 => Offsets.FieldItem + FieldItemLayerSize; public int FieldItemFlag0 => Offsets.FieldItem + (FieldItemLayerSize * 2); public int FieldItemFlag1 => Offsets.FieldItem + (FieldItemLayerSize * 2) + FieldItemFlagSize; public Memory FieldItemFlag0Data => Data.Slice(FieldItemFlag0, FieldItemFlagSize).ToArray(); public Memory FieldItemFlag1Data => Data.Slice(FieldItemFlag1, FieldItemFlagSize).ToArray(); public Item[] GetFieldItemLayer0() => Item.GetArray(Data.Slice(FieldItemLayer0, FieldItemLayerSize)); public void SetFieldItemLayer0(IReadOnlyList array) => Item.SetArray(array).CopyTo(Data[FieldItemLayer0..]); public Item[] GetFieldItemLayer1() => Item.GetArray(Data.Slice(FieldItemLayer1, FieldItemLayerSize)); public void SetFieldItemLayer1(IReadOnlyList array) => Item.SetArray(array).CopyTo(Data[FieldItemLayer1..]); public ushort OutsideFieldTemplateUniqueId { get => ReadUInt16LittleEndian(Data[(Offsets.OutsideField + AcreSizeAll)..]); set => WriteUInt16LittleEndian(Data[(Offsets.OutsideField + AcreSizeAll)..], value); } public ushort MainFieldParamUniqueID { get => ReadUInt16LittleEndian(Data[(Offsets.OutsideField + AcreSizeAll + 2)..]); set => WriteUInt16LittleEndian(Data[(Offsets.OutsideField + AcreSizeAll + 2)..], value); } public uint EventPlazaLeftUpX { get => ReadUInt32LittleEndian(Data[(Offsets.OutsideField + AcreSizeAll + 4)..]); set => WriteUInt32LittleEndian(Data[(Offsets.OutsideField + AcreSizeAll + 4)..], value); } public uint EventPlazaLeftUpZ { get => ReadUInt32LittleEndian(Data[(Offsets.OutsideField + AcreSizeAll + 8)..]); set => WriteUInt32LittleEndian(Data[(Offsets.OutsideField + AcreSizeAll + 8)..], value); } public GSaveVisitorNpc Visitor { get => Data.Slice(Offsets.Visitor, GSaveVisitorNpc.SIZE).ToArray().ToClass(); set => value.ToBytesClass().CopyTo(Data[Offsets.Visitor..]); } public GSaveFg SaveFg { get => Data.Slice(Offsets.SaveFg, GSaveFg.SIZE).ToArray().ToClass(); set => value.ToBytesClass().CopyTo(Data[Offsets.SaveFg..]); } public ushort SpecialtyFruit { get => ReadUInt16LittleEndian(Data[Offsets.SpecialtyFruit..]); set => WriteUInt16LittleEndian(Data[Offsets.SpecialtyFruit..], value); } public ushort SisterFruit { get => ReadUInt16LittleEndian(Data[Offsets.SisterFruit..]); set => WriteUInt16LittleEndian(Data[Offsets.SisterFruit..], value); } public IslandFlowers SpecialtyFlower { get => (IslandFlowers)Data[Offsets.SpecialtyFlower]; set => Data[Offsets.SpecialtyFlower] = (byte)value; } public IslandFlowers SisterFlower { get => (IslandFlowers)Data[Offsets.SisterFlower]; set => Data[Offsets.SisterFlower] = (byte)value; } public Span FruitFlags { get => Data.Slice(Offsets.FruitFlags, 5); set => value.ToArray().CopyTo(Data[Offsets.FruitFlags..]); } public GSaveTime LastSaved => Data.Slice(Offsets.LastSavedTime, GSaveTime.SIZE).ToStructure(); public GSaveBulletinBoard Bulletin { get => Data.Slice(Offsets.BulletinBoard, GSaveBulletinBoard.SIZE).ToStructure(); set => value.ToBytes().CopyTo(Data[Offsets.BulletinBoard..]); } }