NHSE/NHSE.Core/Save/Files/MainSave.cs
Josh (vector_cmdr) 2bfb5759bd
Add: Tour Hemisphere + Weather (#748)
Added the offset for tour weather area + offsets for tour hemisphere and tour weather seed.
Added to UI.
2026-02-12 23:47:34 +11:00

309 lines
13 KiB
C#

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using static System.Buffers.Binary.BinaryPrimitives;
namespace NHSE.Core;
/// <summary>
/// main.dat
/// </summary>
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<GSaveDate>(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<IVillager> 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<IVillagerHouse> 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<Item> RecycleBin
{
get => Item.GetArray(Data.Slice(Offsets.LostItemBox, MainSaveOffsets.RecycleBinCount * Item.SIZE));
set => Item.SetArray(value).CopyTo(Data[Offsets.LostItemBox..]);
}
public IReadOnlyList<Building> 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<IPlayerHouse> 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 <result.Length; i++)
result[i] = GetDesign(i);
return result;
}
public void SetDesigns(IReadOnlyList<DesignPattern> 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<DesignPatternPRO> 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<DesignPatternPRO> 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<short> EventFlagLand => MemoryMarshal.Cast<byte, short>(Data.Slice(Offsets.EventFlagLand, sizeof(short) * EventFlagsSaveCount));
public TurnipStonk Turnips
{
get => Data.Slice(Offsets.ShopKabu, TurnipStonk.SIZE).ToArray().ToClass<TurnipStonk>();
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<byte> 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<TerrainTile> array) => TerrainTile.SetArray(array).CopyTo(Data[Offsets.LandMakingMap..]);
public const ushort MapDesignNone = 0xF800;
public Memory<byte> MapDesignTileData => Raw.Slice(Offsets.MyDesignMap, (AcreWidth * LayerFieldItem.TilesPerAcreDim) * (AcreHeight * LayerFieldItem.TilesPerAcreDim) * sizeof(ushort));
public ushort[] GetMapDesignTiles() => MemoryMarshal.Cast<byte, ushort>(MapDesignTileData.Span).ToArray();
public void SetMapDesignTiles(ReadOnlySpan<ushort> value) => MemoryMarshal.Cast<ushort, byte>(value).CopyTo(MapDesignTileData.Span);
public void ClearDesignTiles()
{
var tiles = GetMapDesignTiles();
tiles.AsSpan().Fill(MapDesignNone);
SetMapDesignTiles(tiles);
}
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<byte> FieldItemFlag0Data => Data.Slice(FieldItemFlag0, FieldItemFlagSize).ToArray();
public Memory<byte> FieldItemFlag1Data => Data.Slice(FieldItemFlag1, FieldItemFlagSize).ToArray();
public Item[] GetFieldItemLayer0() => Item.GetArray(Data.Slice(FieldItemLayer0, FieldItemLayerSize));
public void SetFieldItemLayer0(IReadOnlyList<Item> array) => Item.SetArray(array).CopyTo(Data[FieldItemLayer0..]);
public Item[] GetFieldItemLayer1() => Item.GetArray(Data.Slice(FieldItemLayer1, FieldItemLayerSize));
public void SetFieldItemLayer1(IReadOnlyList<Item> 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<GSaveVisitorNpc>();
set => value.ToBytesClass().CopyTo(Data[Offsets.Visitor..]);
}
public GSaveFg SaveFg
{
get => Data.Slice(Offsets.SaveFg, GSaveFg.SIZE).ToArray().ToClass<GSaveFg>();
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<byte> FruitFlags
{
get => Data.Slice(Offsets.FruitFlags, 5);
set => value.ToArray().CopyTo(Data[Offsets.FruitFlags..]);
}
public GSaveTime LastSaved => Data.Slice(Offsets.LastSavedTime, GSaveTime.SIZE).ToStructure<GSaveTime>();
public GSaveBulletinBoard Bulletin
{
get => Data.Slice(Offsets.BulletinBoard, GSaveBulletinBoard.SIZE).ToStructure<GSaveBulletinBoard>();
set => value.ToBytes().CopyTo(Data[Offsets.BulletinBoard..]);
}
}