Misc tweaks, sealed classes

This commit is contained in:
Kurt 2026-01-17 22:12:37 -06:00
parent 666912a93b
commit 7ea5c03aa3
78 changed files with 187 additions and 331 deletions

View File

@ -7,7 +7,7 @@
namespace NHSE.Core;
public class ItemMutator : BatchMutator<Item>
public sealed class ItemMutator : BatchMutator<Item>
{
public readonly ItemReflection Reflect = ItemReflection.Default;
private const char CONST_POINTER = '*';

View File

@ -4,7 +4,7 @@
namespace NHSE.Core;
public class ItemProcessor(BatchMutator<Item> mut) : BatchProcessor<Item>(mut)
public sealed class ItemProcessor(BatchMutator<Item> mut) : BatchProcessor<Item>(mut)
{
protected override bool CanModify(Item item) => true;
protected override bool Finalize(Item item) => true;

View File

@ -5,7 +5,7 @@
namespace NHSE.Core;
public class ItemReflection
public sealed class ItemReflection
{
public static ItemReflection Default { get; } = new();

View File

@ -11,11 +11,10 @@ namespace NHSE.Core;
/// Creates a HorizonSave from a file provider.
/// </remarks>
/// <param name="provider">Provider for reading/writing save files.</param>
public class HorizonSave(ISaveFileProvider provider)
public sealed class HorizonSave(ISaveFileProvider provider)
{
public readonly MainSave Main = new(provider);
public readonly Player[] Players = Player.ReadMany(provider);
private readonly ISaveFileProvider Provider = provider;
public readonly IReadOnlyList<Player> Players = Player.ReadMany(provider);
public override string ToString() => $"{Players[0].Personal.TownName} - {Players[0]}";
@ -57,7 +56,7 @@ public void Save(uint seed)
pair.Save(seed);
}
}
Provider.Flush();
provider.Flush();
}
/// <summary>

View File

@ -5,7 +5,7 @@ namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets10 : MainSaveOffsets
public sealed class MainSaveOffsets10 : MainSaveOffsets
{
#region GSaveLand
public const int GSaveLandStart = 0x108;

View File

@ -5,7 +5,7 @@ namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets11 : MainSaveOffsets
public sealed class MainSaveOffsets11 : MainSaveOffsets
{
#region GSaveLand
public const int GSaveLandStart = 0x110;

View File

@ -5,7 +5,7 @@ namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets110 : MainSaveOffsets
public sealed class MainSaveOffsets110 : MainSaveOffsets
{
public override int PatternCount => PatternCount2;

View File

@ -6,7 +6,7 @@ namespace NHSE.Core;
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
/// <remarks>Same as <see cref="MainSaveOffsets110"/></remarks>
public class MainSaveOffsets111 : MainSaveOffsets
public sealed class MainSaveOffsets111 : MainSaveOffsets
{
public override int PatternCount => PatternCount2;

View File

@ -5,7 +5,7 @@ namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets12 : MainSaveOffsets
public sealed class MainSaveOffsets12 : MainSaveOffsets
{
#region GSaveLand
public const int GSaveLandStart = 0x110;

View File

@ -5,7 +5,7 @@ namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets13 : MainSaveOffsets
public sealed class MainSaveOffsets13 : MainSaveOffsets
{
#region GSaveLand
public const int GSaveLandStart = 0x110;

View File

@ -5,7 +5,7 @@ namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets14 : MainSaveOffsets
public sealed class MainSaveOffsets14 : MainSaveOffsets
{
#region GSaveLand
public const int GSaveLandStart = 0x110;

View File

@ -5,7 +5,7 @@ namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets15 : MainSaveOffsets
public sealed class MainSaveOffsets15 : MainSaveOffsets
{
#region GSaveLand
public const int GSaveLandStart = 0x110;

View File

@ -5,7 +5,7 @@ namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets16 : MainSaveOffsets
public sealed class MainSaveOffsets16 : MainSaveOffsets
{
#region GSaveLand
public const int GSaveLandStart = 0x110;

View File

@ -5,7 +5,7 @@ namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets17 : MainSaveOffsets
public sealed class MainSaveOffsets17 : MainSaveOffsets
{
#region GSaveLand
public const int GSaveLandStart = 0x110;

View File

@ -6,7 +6,7 @@ namespace NHSE.Core;
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
/// <remarks>Same as <see cref="MainSaveOffsets17"/></remarks>.
public class MainSaveOffsets18 : MainSaveOffsets
public sealed class MainSaveOffsets18 : MainSaveOffsets
{
#region GSaveLand
public const int GSaveLandStart = 0x110;

View File

@ -5,7 +5,7 @@ namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets19 : MainSaveOffsets
public sealed class MainSaveOffsets19 : MainSaveOffsets
{
public override int PatternCount => PatternCount2;

View File

@ -6,7 +6,7 @@ namespace NHSE.Core;
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
/// <remarks>Same as <see cref="MainSaveOffsets110"/></remarks>
public class MainSaveOffsets20 : MainSaveOffsets
public sealed class MainSaveOffsets20 : MainSaveOffsets
{
public override int PatternCount => PatternCount2;

View File

@ -5,7 +5,7 @@ namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets30 : MainSaveOffsets
public sealed class MainSaveOffsets30 : MainSaveOffsets
{
public override int PatternCount => PatternCount2;

View File

@ -8,7 +8,7 @@ namespace NHSE.Core;
/// Interact-able structure that can be entered by the player.
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = SIZE, Pack = 1)]
public class Building
public sealed class Building
{
public const int SIZE = 0x14;

View File

@ -35,4 +35,6 @@ public enum BuildingType : ushort
Incline = 27,
ReddsTreasureTrawler = 28,
Studio = 29,
Hotel = 42,
}

View File

@ -6,7 +6,7 @@ namespace NHSE.Core;
/// <summary>
/// Simple design pattern
/// </summary>
public class DesignPattern(Memory<byte> Raw) : IVillagerOrigin
public sealed class DesignPattern(Memory<byte> Raw) : IVillagerOrigin
{
public const int Width = 32;
public const int Height = 32;

View File

@ -6,7 +6,7 @@ namespace NHSE.Core;
/// <summary>
/// Advanced design pattern with 4 sheets arranged in a square.
/// </summary>
public class DesignPatternPRO(Memory<byte> Raw) : IVillagerOrigin
public sealed class DesignPatternPRO(Memory<byte> Raw) : IVillagerOrigin
{
public const int Width = 32;
public const int Height = 32;

View File

@ -75,7 +75,7 @@ public static void DumpPlayerHouses(this IReadOnlyList<IPlayerHouse> houses, IRe
/// <param name="path">Path to dump to</param>
public static void DumpPlayerHouses(this HorizonSave sav, string path)
{
var count = Math.Min(sav.Players.Length, MainSaveOffsets.PlayerCount);
var count = Math.Min(sav.Players.Count, MainSaveOffsets.PlayerCount);
for (int i = 0; i < count; i++)
{
var p = sav.Players[i];

View File

@ -3,10 +3,9 @@
namespace NHSE.Core;
public class ItemArrayEditor<T> where T : Item, ICopyableItem<T>
public sealed record ItemArrayEditor<T>(IReadOnlyList<T> Items)
where T : Item, ICopyableItem<T>
{
public readonly IReadOnlyList<T> Items;
public ItemArrayEditor(IReadOnlyList<T> items) => Items = items;
public int ItemSize => Items[0].Size;
public int TotalSize => Items.Count * ItemSize;

View File

@ -5,38 +5,19 @@ namespace NHSE.Core;
/// <summary>
/// Metadata for an item's customization permissions
/// </summary>
public class ItemRemakeInfo
public sealed record ItemRemakeInfo(
short Index,
ushort ItemUniqueID,
sbyte ReBodyPatternNum,
byte[] ReBodyPatternColors0,
byte[] ReBodyPatternColors1,
byte[] ReFabricPatternColors0,
byte[] ReFabricPatternColors1,
bool ReFabricPattern0VisibleOff)
{
public const int BodyColorCountMax = 8;
public const int NoColor = (int)ItemCustomColor.None; // 14
public readonly short Index;
public readonly ushort ItemUniqueID;
public readonly sbyte ReBodyPatternNum; // count of body colors
public readonly byte[] ReBodyPatternColors0;
public readonly byte[] ReBodyPatternColors1;
public readonly byte[] ReFabricPatternColors0;
public readonly byte[] ReFabricPatternColors1;
public readonly bool ReFabricPattern0VisibleOff;
public ItemRemakeInfo(short index, ushort id, sbyte count, byte[] bc0, byte[] bc1, byte[] fc0, byte[] fc1, bool fp0)
{
Index = index;
ItemUniqueID = id;
ReBodyPatternNum = count;
ReBodyPatternColors0 = bc0;
ReBodyPatternColors1 = bc1;
ReFabricPatternColors0 = fc0;
ReFabricPatternColors1 = fc1;
ReFabricPattern0VisibleOff = fp0;
}
private const string Invalid = nameof(Invalid);
public bool HasBodyColor(int variant) => ReBodyPatternColors0[variant] != NoColor || ReBodyPatternColors1[variant] != NoColor;

View File

@ -6,7 +6,7 @@ namespace NHSE.Core;
/// <summary>
/// Manages the <see cref="Item"/> data for the player's outside overworld.
/// </summary>
public class FieldItemManager
public sealed class FieldItemManager
{
/// <summary>
/// Base layer of items

View File

@ -4,7 +4,7 @@
namespace NHSE.Core;
public class RoomItemManager
public sealed class RoomItemManager
{
public readonly RoomItemLayer[] Layers;

View File

@ -9,7 +9,7 @@ namespace NHSE.Core;
/// Represents a Terraform-able terrain tile.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public class TerrainTile
public sealed class TerrainTile
{
// tile[2] (u16 model, u16 variation, u16 angle)
// u16 elevation

View File

@ -4,7 +4,7 @@
namespace NHSE.Core;
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public class GSaveFg
public sealed class GSaveFg
{
public const int SIZE = 0x928;
private const int _7b9816fbCount = 0x900;

View File

@ -5,7 +5,7 @@
namespace NHSE.Core;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class GSaveVisitorNpc
public sealed class GSaveVisitorNpc
{
public const int SIZE = 0x78;
private const int Days = 7;
@ -46,7 +46,7 @@ public struct V3f
public float Y { get; set; }
public float Z { get; set; }
public override string ToString() => $"({X},{Y},{Z})";
public readonly override string ToString() => $"({X},{Y},{Z})";
}
public enum VisitorNPC

View File

@ -2,7 +2,7 @@
namespace NHSE.Core;
public class MapManager(MainSave sav) : MapTerrainStructure(sav)
public sealed class MapManager(MainSave sav) : MapTerrainStructure(sav)
{
public readonly FieldItemManager Items = new(sav);
@ -11,7 +11,7 @@ public class MapManager(MainSave sav) : MapTerrainStructure(sav)
public FieldItemLayer CurrentLayer => MapLayer == 0 ? Items.Layer1 : Items.Layer2;
}
public class MapTerrainStructure(MainSave sav)
public abstract class MapTerrainStructure(MainSave sav)
{
public readonly TerrainLayer Terrain = new(sav.GetTerrainTiles(), sav.GetAcreBytes());
public readonly IReadOnlyList<Building> Buildings = sav.Buildings;

View File

@ -2,25 +2,19 @@
namespace NHSE.Core;
public class MapView
public abstract class MapView(MapManager m, int scale = 16)
{
private const int ViewInterval = 2;
public readonly MapManager Map;
public readonly MapManager Map = m;
public int MapScale { get; } = 1;
public int AcreScale { get; }
public int AcreScale { get; } = scale;
public int TerrainScale => AcreScale * 2;
// Top Left Anchor Coordinates
public int X { get; set; }
public int Y { get; set; }
protected MapView(MapManager m, int scale = 16)
{
AcreScale = scale;
Map = m;
}
public bool CanUp => Y != 0;
public bool CanDown => Y < Map.CurrentLayer.TileInfo.TotalHeight - Map.CurrentLayer.TileInfo.ViewHeight;
public bool CanLeft => X != 0;

View File

@ -4,15 +4,12 @@
namespace NHSE.Core;
public class Museum
public sealed class Museum(Memory<byte> raw)
{
public const int SIZE = 0x3404;
public const int EntryCount = 1024;
public readonly Memory<byte> Raw;
public Span<byte> Data => Raw.Span;
public Museum(Memory<byte> data) => Raw = data;
public Span<byte> Data => raw.Span;
public int MuseumLevel
{

View File

@ -5,27 +5,17 @@
namespace NHSE.Core;
public class MuseumEditor
public sealed record MuseumEditor(Museum Museum)
{
public readonly Museum Museum;
public readonly GSaveDate[] Dates;
public readonly Item[] Items;
public readonly byte[] Players;
public MuseumEditor(Museum museum)
{
Museum = museum;
Dates = museum.GetDates();
Items = museum.GetItems();
Players = museum.GetPlayers();
}
public readonly GSaveDate[] Dates = Museum.GetDates();
public readonly Item[] Items = Museum.GetItems();
public readonly byte[] Players = Museum.GetPlayers();
public void Save()
{
var museum = Museum;
museum.SetDates(Dates);
museum.SetItems(Items);
museum.SetPlayers(Players);
Museum.SetDates(Dates);
Museum.SetItems(Items);
Museum.SetPlayers(Players);
}
public IEnumerable<string> GetDonationSummary(GameStrings str)

View File

@ -3,16 +3,14 @@
namespace NHSE.Core;
public class RecipeBook
public sealed class RecipeBook(Memory<byte> raw)
{
private const int BitFlagArraySize = 0x100;
private const int BitFlagArrayCount = 4;
public const int SIZE = BitFlagArraySize * BitFlagArrayCount;
public const ushort RecipeCount = BitFlagArraySize * 8;
private readonly Memory<byte> Raw;
private Span<byte> Data => Raw.Span;
public RecipeBook(Memory<byte> raw) => Raw = raw;
private Span<byte> Data => raw.Span;
public void Save(Span<byte> data) => Data.CopyTo(data);

View File

@ -7,7 +7,7 @@ namespace NHSE.Core;
/// <summary>
/// Used for allowing a struct to be mutated in a PropertyGrid.
/// </summary>
public class ValueTypeTypeConverter : ExpandableObjectConverter
public sealed class ValueTypeTypeConverter : ExpandableObjectConverter
{
public override bool GetCreateInstanceSupported(ITypeDescriptorContext? context) => true;

View File

@ -6,51 +6,24 @@ namespace NHSE.Core;
/// <summary>
/// Multi-milestone definition for tracking game-play achievements.
/// </summary>
public class LifeSupportAchievement : INamedValue
public sealed record LifeSupportAchievement(
ushort Index,
byte AchievementCount,
uint Threshold1,
uint Threshold2,
uint Threshold3,
uint Threshold4,
uint Threshold5,
short FlagLand,
short FlagPlayer,
string Name)
: INamedValue
{
/// <summary>
/// Amount of milestones an achievement can have.
/// </summary>
public const int MilestoneMax = 6;
public readonly short FlagLand;
public readonly short FlagPlayer;
public ushort Index { get; }
public string Name { get; }
/// <summary> Total number of milestones for this achievement type. </summary>
public readonly int AchievementCount;
/// <summary> First Milestone's Satisfaction Threshold </summary>
public readonly uint Threshold1;
/// <summary> Second Milestone's Satisfaction Threshold </summary>
public readonly uint Threshold2;
/// <summary> Third Milestone's Satisfaction Threshold </summary>
public readonly uint Threshold3;
/// <summary> Fourth Milestone's Satisfaction Threshold </summary>
public readonly uint Threshold4;
/// <summary> Fifth Milestone's Satisfaction Threshold </summary>
public readonly uint Threshold5;
public LifeSupportAchievement(ushort index, byte max, uint t1, uint t2, uint t3, uint t4, uint t5, short land, short player, string name)
{
Index = index;
AchievementCount = max;
Threshold1 = t1;
Threshold2 = t2;
Threshold3 = t3;
Threshold4 = t4;
Threshold5 = t5;
FlagLand = land;
FlagPlayer = player;
Name = name;
}
public uint MaxThreshold => Math.Max(Threshold1, Math.Max(Threshold2, Math.Max(Threshold3, Math.Max(Threshold4, Threshold5))));
/// <summary>

View File

@ -4,7 +4,7 @@
namespace NHSE.Core;
[StructLayout(LayoutKind.Sequential, Size = SIZE)]
public class TurnipStonk // GSaveShopKabu
public sealed class TurnipStonk // GSaveShopKabu
{
public const int SIZE = 0x44;

View File

@ -3,14 +3,11 @@
namespace NHSE.Core;
public class GSaveMemory : IVillagerOrigin
public sealed class GSaveMemory(Memory<byte> raw) : IVillagerOrigin
{
public const int SIZE = 0x5F0;
public readonly Memory<byte> Raw;
public Span<byte> Data => Raw.Span;
public GSaveMemory(Memory<byte> data) => Raw = data;
public Span<byte> Data => raw.Span;
public GSavePlayerId PlayerId
{

View File

@ -4,15 +4,12 @@
namespace NHSE.Core;
public class PlayerHouse1 : IPlayerHouse
public class PlayerHouse1(Memory<byte> raw) : IPlayerHouse
{
public const int SIZE = 0x26400;
public virtual string Extension => "nhph";
public readonly Memory<byte> Raw;
public Span<byte> Data => Raw.Span;
public PlayerHouse1(Memory<byte> data) => Raw = data;
public Span<byte> Data => raw.Span;
public byte[] Write() => Data.ToArray();

View File

@ -2,13 +2,11 @@
namespace NHSE.Core;
public class PlayerHouse2 : PlayerHouse1
public sealed class PlayerHouse2(Memory<byte> raw) : PlayerHouse1(raw)
{
public new const int SIZE = 0x28A28;
public override string Extension => "nhph2";
public PlayerHouse2(Memory<byte> data) : base(data) { }
public override IPlayerRoom GetRoom(int roomIndex)
{
if ((uint)roomIndex >= MaxRoom)

View File

@ -4,10 +4,10 @@
namespace NHSE.Core;
public class PlayerRoom2(Memory<byte> raw) : PlayerRoom1(raw)
public sealed class PlayerRoom2(Memory<byte> raw) : PlayerRoom1(raw)
{
public new const int SIZE = 0x6C24;
public new virtual string Extension => "nhpr2";
public override string Extension => "nhpr2";
/*
s_665e9093 ExtraEffectLayerList[2]; // @0x65c8 size 0x320, align 2
@ -47,7 +47,7 @@ public GSaveMusicBoxInfo MusicBoxInfo
// 3 bytes padding
public s_e13a81f4 _cfb139b9
public s_e13a81f4 Unk_cfb139b9
{
get => Data.Slice(0x6C10, s_e13a81f4.SIZE).ToStructure<s_e13a81f4>();
set => value.ToBytes().CopyTo(Data[0x6C10..]);

View File

@ -8,14 +8,12 @@ namespace NHSE.Core;
/// <summary>
/// Villager object format from 1.0 to update 1.4
/// </summary>
public sealed class Villager1 : IVillager
public sealed class Villager1(Memory<byte> raw) : IVillager
{
public const int SIZE = 0x12AB0;
public string Extension => "nhv";
public readonly Memory<byte> Raw;
public Span<byte> Data => Raw.Span;
public Villager1(Memory<byte> data) => Raw = data;
public Span<byte> Data => raw.Span;
public byte[] Write() => Data.ToArray();
public byte Species { get => Data[0]; set => Data[0] = value; }
@ -34,7 +32,7 @@ public GSaveMemory GetMemory(int index)
if ((uint) index >= PlayerMemoryCount)
throw new ArgumentOutOfRangeException(nameof(index));
var bytes = Raw.Slice(0x4 + (index * GSaveMemory.SIZE), GSaveMemory.SIZE);
var bytes = raw.Slice(0x4 + (index * GSaveMemory.SIZE), GSaveMemory.SIZE);
return new GSaveMemory(bytes);
}
@ -115,7 +113,7 @@ public GSaveRoomFloorWall Room
public DesignPatternPRO Design
{
get => new(Raw.Slice(0x12128, DesignPatternPRO.SIZE));
get => new(raw.Slice(0x12128, DesignPatternPRO.SIZE));
set => value.Data.CopyTo(Data[0x12128..]);
}

View File

@ -3,15 +3,13 @@
namespace NHSE.Core;
public class VillagerHouse1 : IVillagerHouse
public class VillagerHouse1(Memory<byte> raw) : IVillagerHouse
{
public const int SIZE = 0x1D4;
public const int ItemCount = 36;
public virtual string Extension => "nhvh";
public readonly Memory<byte> Raw;
public VillagerHouse1(Memory<byte> raw) => Raw = raw;
public Span<byte> Data => Raw.Span;
public Span<byte> Data => raw.Span;
public byte[] Write() => Data.ToArray();

View File

@ -5,13 +5,11 @@
namespace NHSE.Core;
public class VillagerHouse2 : VillagerHouse1
public sealed class VillagerHouse2(Memory<byte> raw) : VillagerHouse1(raw)
{
public new const int SIZE = 0x12E8;
public override string Extension => "nhvh2";
public VillagerHouse2(Memory<byte> data) : base(data) { }
// 0x1D4-0x12DB -- 0x1108 sized structure
// 0x12DC -- 8 byte item
// 0x12E4 -- 1 byte

View File

@ -6,7 +6,7 @@ namespace NHSE.Core;
/// <summary>
/// Key Value pair for a displayed <see cref="T:System.String" /> and underlying <see cref="T:System.Int32" /> value.
/// </summary>
public record ComboItem(string Text, int Value);
public sealed record ComboItem(string Text, int Value);
public static class ComboItemUtil
{
@ -25,10 +25,10 @@ public static List<ComboItem> GetArray(ReadOnlySpan<string> items)
return result;
}
public static List<ComboItem> GetArray<T>(Type t) where T : struct, IFormattable
public static List<ComboItem> GetArray<T>() where T : struct, Enum, IFormattable
{
var names = Enum.GetNames(t);
var values = (T[])Enum.GetValues(t);
var names = Enum.GetNames<T>();
var values = Enum.GetValues<T>();
var acres = new List<ComboItem>(names.Length);
for (int i = 0; i < names.Length; i++)

View File

@ -3,12 +3,8 @@
namespace NHSE.Injection;
public class AutoInjector
public sealed record AutoInjector(IDataInjector Injector, Action<InjectionResult> DoRead, Action<InjectionResult> DoWrite)
{
public readonly IDataInjector Injector;
private readonly Action<InjectionResult> AfterRead;
private readonly Action<InjectionResult> AfterWrite;
public bool AutoInjectEnabled { private get; set; }
public bool ValidateEnabled
@ -17,13 +13,6 @@ public bool ValidateEnabled
set => Injector.ValidateEnabled = value;
}
public AutoInjector(IDataInjector inj, Action<InjectionResult> read, Action<InjectionResult> write)
{
Injector = inj;
AfterRead = read;
AfterWrite = write;
}
public void Validate() => Injector.Validate();
public InjectionResult Read(bool force = false)
@ -34,7 +23,7 @@ public InjectionResult Read(bool force = false)
try
{
var result = Injector.Read();
AfterRead(result);
DoRead(result);
return result;
}
catch (IndexOutOfRangeException ex)
@ -51,7 +40,7 @@ public InjectionResult Write(bool force = false)
try
{
var result = Injector.Write();
AfterWrite(result);
DoWrite(result);
return result;
}
catch (IndexOutOfRangeException ex)

View File

@ -1,35 +1,27 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using NHSE.Core;
namespace NHSE.Injection;
public class PocketInjector : IDataInjector
public sealed class PocketInjector(IReadOnlyList<Item> items, IRAMReadWriter bot) : IDataInjector
{
private readonly IReadOnlyList<Item> Items;
private readonly IRAMReadWriter Bot;
public bool Connected => Bot.Connected;
public bool Connected => bot.Connected;
private byte[]? LastData { get; set; }
public uint WriteOffset { private get; set; }
public bool ValidateEnabled { get; set; } = true;
public bool SpoofInventoryWrite { get; set; }
private static readonly Item DroppableOnlyItem = new(0x9C9); // Gold nugget
public PocketInjector(IReadOnlyList<Item> items, IRAMReadWriter bot)
{
Items = items;
Bot = bot;
}
private static readonly Item DroppableOnlyItem = new(0x9C9); // Gold nugget
public bool ReadValidate(out byte[] data)
{
PlayerItemSet.GetOffsetLength(WriteOffset, out var offset, out var size);
data = Bot.ReadBytes(offset, size);
data = bot.ReadBytes(offset, size);
return Validate(data);
}
private byte[]? LastData;
public InjectionResult Read()
{
if (!ReadValidate(out var data))
@ -38,7 +30,7 @@ public InjectionResult Read()
if (LastData?.SequenceEqual(data) == true)
return InjectionResult.Same;
PlayerItemSet.ReadPlayerInventory(data, Items);
PlayerItemSet.ReadPlayerInventory(data, items);
LastData = data;
@ -52,9 +44,9 @@ public InjectionResult Write()
var orig = (byte[])data.Clone();
var items = !SpoofInventoryWrite ? Items : Enumerable.Repeat(DroppableOnlyItem, Items.Count).ToArray();
var items1 = !SpoofInventoryWrite ? items : Enumerable.Repeat(DroppableOnlyItem, items.Count).ToArray();
PlayerItemSet.WritePlayerInventory(data, items);
PlayerItemSet.WritePlayerInventory(data, items1);
if (data.SequenceEqual(orig))
return InjectionResult.Same;
@ -63,7 +55,7 @@ public InjectionResult Write()
if (size != data.Length)
return InjectionResult.FailBadSize;
Bot.WriteBytes(data, offset);
bot.WriteBytes(data, offset);
LastData = data;

View File

@ -3,7 +3,7 @@
namespace NHSE.Injection;
public class SysBot : IRAMReadWriter
public sealed class SysBot : IRAMReadWriter
{
public string IP = "192.168.1.65";
public int Port = 6000;

View File

@ -6,7 +6,7 @@
namespace NHSE.Injection;
public class USBBot : IRAMReadWriter
public sealed class USBBot : IRAMReadWriter
{
private UsbDevice? SwDevice;
private UsbEndpointReader? reader;

View File

@ -7,7 +7,7 @@
namespace NHSE.Parsing;
public class BCSV
public sealed class BCSV
{
public static readonly BCSVEnumDictionary EnumLookup = new(Resources.specs_130.Split('\n'));
public static bool DecodeColumnNames { private get; set; } = true;

View File

@ -5,9 +5,9 @@
namespace NHSE.Parsing;
public class BCSVEnumDictionary
public sealed class BCSVEnumDictionary
{
private readonly Dictionary<uint, string> Lookup = [];
private readonly Dictionary<uint, string> _lookup = [];
public BCSVEnumDictionary(IEnumerable<string> lines)
{
@ -35,7 +35,7 @@ private void AddColumnName(string trim)
var slice = value.AsSpan(2, value.Length - 3);
var hex = StringUtil.GetHexValue(slice);
if (Lookup.TryGetValue(hex, out var exist))
if (_lookup.TryGetValue(hex, out var exist))
{
if (exist == name)
return;
@ -43,7 +43,7 @@ private void AddColumnName(string trim)
return;
}
Lookup.Add(hex, name);
_lookup.Add(hex, name);
}
private void AddEnumName(string trim)
@ -56,7 +56,7 @@ private void AddEnumName(string trim)
return;
var hash = CRC32.Compute(text);
if (Lookup.TryGetValue(hash, out var exist))
if (_lookup.TryGetValue(hash, out var exist))
{
if (exist == text)
return;
@ -64,10 +64,10 @@ private void AddEnumName(string trim)
return;
}
Lookup.Add(hash, text);
_lookup.Add(hash, text);
}
public IEnumerable<string> Dump() => Lookup.Select(z => $"{z.Key:X8}\t{z.Value}");
public IEnumerable<string> Dump() => _lookup.Select(z => $"{z.Key:X8}\t{z.Value}");
public string this[uint key] => Lookup.TryGetValue(key, out var val) ? val : $"0x{key:X8}";
public string this[uint key] => _lookup.TryGetValue(key, out var val) ? val : $"0x{key:X8}";
}

View File

@ -1,16 +1,6 @@
namespace NHSE.Parsing;
public class BCSVFieldParam
public sealed record BCSVFieldParam(uint ColumnKey, int Offset, int Index)
{
public const int SIZE = 8;
public readonly uint ColumnKey;
public readonly int Offset;
public readonly int Index;
public BCSVFieldParam(uint key, int offset, int index)
{
ColumnKey = key;
Offset = offset;
Index = index;
}
}

View File

@ -14,7 +14,7 @@ public static class GameMSBTDumper
/// <param name="dest">Destination folder where the dumps will be saved.</param>
/// <param name="csv">Convert all <see cref="MSBT"/> files to CSV for easy viewing.</param>
/// <param name="delim">Delimiter when exporting the <see cref="csv"/> files</param>
public static void UpdateDumps(string root, string dest, bool csv = true, char delim = '\t')
public static void UpdateDumps(string root, string dest, bool csv = false, char delim = '\t')
{
if (csv)
UpdateCSV(root, Path.Combine(dest, "csv"), delim);

View File

@ -2,7 +2,7 @@
namespace NHSE.Parsing;
public class LBL1() : MSBTSection(string.Empty, [])
public sealed class LBL1() : MSBTSection(string.Empty, [])
{
public uint NumberOfGroups;

View File

@ -5,7 +5,7 @@
namespace NHSE.Parsing;
public class MSBT
public sealed class MSBT
{
public readonly MSBTHeader Header;
public readonly LBL1 LBL1 = new();
@ -78,7 +78,7 @@ private void ReadLBL1(BinaryReaderX br)
var length = Convert.ToUInt32(br.ReadByte());
var name = br.ReadString((int)length);
var index = br.ReadUInt32();
var lbl = new MSBTLabel(name) {Index = index, Length = length};
var lbl = new MSBTLabel {Name = name, Index = index, Length = length};
LBL1.Labels.Add(lbl);
}
}

View File

@ -1,7 +1,7 @@
namespace NHSE.Parsing;
public class MSBTGroup
public sealed record MSBTGroup
{
public uint NumberOfLabels;
public uint Offset;
public required uint NumberOfLabels { get; init; }
public required uint Offset { get; init; }
}

View File

@ -2,7 +2,7 @@
namespace NHSE.Parsing;
public class MSBTHeader
public sealed class MSBTHeader
{
public readonly string Identifier; // MsgStdBn
public readonly byte[] ByteOrderMark;

View File

@ -2,19 +2,14 @@
namespace NHSE.Parsing;
public class MSBTLabel
public sealed record MSBTLabel
{
public uint Length;
public readonly string Name;
public MSBTTextString String;
public required uint Length { get; init; }
public required uint Index { get; init; }
public required string Name { get; init; }
public MSBTLabel(string name)
{
Name = name;
String = MSBTTextString.Empty;
}
public MSBTTextString String { get; set; } = MSBTTextString.Empty;
public uint Index { get; set; }
public override string ToString() => Length > 0 ? Name : (Index + 1).ToString();
public string ToString(Encoding encoding) => encoding.GetString(String.Value.Span);
}

View File

@ -1,14 +1,8 @@
namespace NHSE.Parsing;
public class MSBTSection
public abstract class MSBTSection(string identifier, byte[] padding)
{
public string Identifier;
public uint SectionSize; // Begins after Unknown1
public byte[] Padding1; // Always 0x0000 0000
public MSBTSection(string identifier, byte[] padding)
{
Identifier = identifier;
Padding1 = padding;
}
public string Identifier { get; set; } = identifier;
public uint SectionSize { get; set; } // Begins after Unknown1
public byte[] Padding1 { get; set; } = padding; // Always 0x0000 0000
}

View File

@ -4,19 +4,10 @@
namespace NHSE.Parsing;
public class MSBTTextString
public sealed record MSBTTextString(Memory<byte> Value, uint Index)
{
public readonly Memory<byte> Value;
public readonly uint Index;
public static readonly MSBTTextString Empty = new(default, 0);
public MSBTTextString(Memory<byte> v, uint i)
{
Value = v;
Index = i;
}
public override string ToString() => (Index + 1).ToString();
public string ToString(Encoding encoding) => encoding.GetString(Value.Span);

View File

@ -2,7 +2,7 @@
namespace NHSE.Parsing;
public class TXT2() : MSBTSection(string.Empty, [])
public sealed class TXT2() : MSBTSection(string.Empty, [])
{
public uint NumberOfStrings;

View File

@ -6,7 +6,7 @@
namespace NHSE.Parsing;
public class PBC
public sealed class PBC
{
private const uint MAGIC = 0x00636270; // pbc\0

View File

@ -1,4 +1,5 @@
using System.IO;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
namespace NHSE.Parsing;
@ -22,7 +23,7 @@ public static void ConvertItemStrings(string input, string output)
private static string[] ConvertItemList(string path)
{
var lines = File.ReadAllLines(path);
var items = lines.Select(z => new ParseItem(z)).ToArray();
var items = lines.Select(ParseItem.FromString).ToArray();
var max = items.Max(z => z.Index);
var result = new string[max + 1];
@ -32,14 +33,18 @@ private static string[] ConvertItemList(string path)
}
}
public class ParseItem
public readonly record struct ParseItem
{
public readonly int Index;
public readonly string Name;
public required int Index {get; init; }
public required string Name { get; init; }
[SetsRequiredMembers]
public ParseItem(string line)
{
var split = line.Split(", ");
Index = int.Parse(split[0], System.Globalization.NumberStyles.HexNumber);
Name = split[1];
}
public static ParseItem FromString(string s) => new(s);
}

View File

@ -6,10 +6,10 @@
namespace NHSE.Tests;
public class ArrayUtilTests
public static class ArrayUtilTests
{
[Fact]
public void ReplaceOccurrences_WhenPatternNotFound_ReturnsZero()
public static void ReplaceOccurrences_WhenPatternNotFound_ReturnsZero()
{
byte[] array = [0x01, 0x02, 0x03, 0x04, 0x05];
byte[] pattern = [0xAA, 0xBB];
@ -22,7 +22,7 @@ public void ReplaceOccurrences_WhenPatternNotFound_ReturnsZero()
}
[Fact]
public void ReplaceOccurrences_WhenSingleOccurrence_ReplacesAndReturnsOne()
public static void ReplaceOccurrences_WhenSingleOccurrence_ReplacesAndReturnsOne()
{
byte[] array = [0x01, 0xAA, 0xBB, 0x04, 0x05];
byte[] pattern = [0xAA, 0xBB];
@ -35,7 +35,7 @@ public void ReplaceOccurrences_WhenSingleOccurrence_ReplacesAndReturnsOne()
}
[Fact]
public void ReplaceOccurrences_WhenMultipleOccurrences_ReplacesAllAndReturnsCount()
public static void ReplaceOccurrences_WhenMultipleOccurrences_ReplacesAllAndReturnsCount()
{
byte[] array = [0xAA, 0xBB, 0x03, 0xAA, 0xBB, 0x06, 0xAA, 0xBB];
byte[] pattern = [0xAA, 0xBB];
@ -48,7 +48,7 @@ public void ReplaceOccurrences_WhenMultipleOccurrences_ReplacesAllAndReturnsCoun
}
[Fact]
public void ReplaceOccurrences_WhenConsecutiveOccurrences_ReplacesAll()
public static void ReplaceOccurrences_WhenConsecutiveOccurrences_ReplacesAll()
{
byte[] array = [0xAA, 0xBB, 0xAA, 0xBB, 0xAA, 0xBB];
byte[] pattern = [0xAA, 0xBB];
@ -61,7 +61,7 @@ public void ReplaceOccurrences_WhenConsecutiveOccurrences_ReplacesAll()
}
[Fact]
public void ReplaceOccurrences_WhenSwapContainsPattern_DoesNotCauseInfiniteLoop()
public static void ReplaceOccurrences_WhenSwapContainsPattern_DoesNotCauseInfiniteLoop()
{
// Swap contains the original pattern - must skip past swapped data
byte[] array = [0x01, 0xAA, 0xBB, 0x04];
@ -75,7 +75,7 @@ public void ReplaceOccurrences_WhenSwapContainsPattern_DoesNotCauseInfiniteLoop(
}
[Fact]
public void ReplaceOccurrences_WhenLargeFileWithRandomPlacements_ReplacesAllOccurrences()
public static void ReplaceOccurrences_WhenLargeFileWithRandomPlacements_ReplacesAllOccurrences()
{
const int fileSize = 1024 * 1024; // 1 MB
const int sequenceLength = 0x13; // 19 bytes

View File

@ -1,14 +1,14 @@
using System.Linq;
using System.Linq;
using FluentAssertions;
using NHSE.Core;
using Xunit;
namespace NHSE.Tests;
public class BuildingTests
public static class BuildingTests
{
[Fact]
public void BuildingMarshal()
public static void BuildingMarshal()
{
var building = new Building();
var bytes = building.ToBytesClass();
@ -16,7 +16,7 @@ public void BuildingMarshal()
}
[Fact]
public void BuildingClear()
public static void BuildingClear()
{
var item = new Building {BuildingType = BuildingType.PlayerHouse1, X=5, Y=7};
var bytes = item.ToBytesClass();

View File

@ -5,10 +5,10 @@
namespace NHSE.Tests;
public class EncryptedIntTests
public static class EncryptedIntTests
{
[Fact]
public void TestParse()
public static void TestParse()
{
const int expect = 31_280;
ReadOnlySpan<byte> data = [0x8A, 0xC4, 0xE3, 0xCF, 0x37, 0xD5, 0x1A, 0xD3];

View File

@ -4,13 +4,13 @@
namespace NHSE.Tests;
public class EnumHashTests
public static class EnumHashTests
{
[Theory]
[InlineData("Base", 0x6086515F)]
[InlineData("River", 0x3422482F)]
[InlineData("RoadStone", 0x13011867)]
public void ChecksumMatches(string str, uint val)
public static void ChecksumMatches(string str, uint val)
{
var computed = CRC32.Compute(str);
computed.Should().Be(val);

View File

@ -1,10 +1,10 @@
using FluentAssertions;
using FluentAssertions;
using NHSE.Core;
using Xunit;
namespace NHSE.Tests;
public class FancyMarshalTests
public sealed class FancyMarshalTests
{
[Fact] public void MarshalGSaveBulletinBoard() => MarshalBytesTestS<GSaveBulletinBoard>(GSaveBulletinBoard.SIZE);
[Fact] public void MarshalBulletinBoard() => MarshalBytesTestS<BulletinBoardStock>(BulletinBoardStock.SIZE);

View File

@ -1,10 +1,10 @@
using FluentAssertions;
using FluentAssertions;
using NHSE.Core;
using Xunit;
namespace NHSE.Tests;
public class MarshalTests
public sealed class MarshalTests
{
[Fact] public void MarshalItem() => MarshalTest<Item>(Item.SIZE);
[Fact] public void MarshalVillagerItem() => MarshalTest<VillagerItem>(VillagerItem.SIZE);

View File

@ -2,14 +2,7 @@
namespace NHSE.Villagers;
public class VillagerData
{
public readonly Memory<byte> Villager;
public readonly Memory<byte> House;
public VillagerData(Memory<byte> villager, Memory<byte> house)
{
Villager = villager;
House = house;
}
}
/// <summary>
/// Tuple-like record struct to hold villager and house memory segments.
/// </summary>
public readonly record struct VillagerData(Memory<byte> Villager, Memory<byte> House);

View File

@ -2,14 +2,4 @@
namespace NHSE.Villagers;
public class VillagerInfo
{
public readonly Villager2 Villager;
public readonly IVillagerHouse House;
public VillagerInfo(Villager2 villager, IVillagerHouse house)
{
Villager = villager;
House = house;
}
}
public readonly record struct VillagerInfo(Villager2 Villager, IVillagerHouse House);

View File

@ -11,7 +11,11 @@ namespace NHSE.WinForms;
public partial class ItemGridEditor : UserControl
{
private static readonly GridSize Sprites = new();
private static readonly GridSize Sprites = new()
{
Width = 64,
Height = 64,
};
private readonly ItemEditor Editor;
private readonly IReadOnlyList<Item> Items;
@ -304,9 +308,9 @@ private void SetEditorItems(List<Item> items)
private void B_ClearFish_Click(object sender, EventArgs e) => ClearItemIf(z => GameLists.Fish.Contains(z.ItemId));
private void B_ClearDive_Click(object sender, EventArgs e) => ClearItemIf(z => GameLists.Dive.Contains(z.ItemId));
private class GridSize : IGridItem
private sealed record GridSize : IGridItem
{
public int Width { get; set; } = 64;
public int Height { get; set; } = 64;
public required int Width { get; set; }
public required int Height { get; set; }
}
}

View File

@ -206,7 +206,7 @@ private void SaveMain()
#region Player Editing
private void LoadPlayers()
{
if (SAV.Players.Length == 0)
if (SAV.Players.Count == 0)
throw new Exception("No players found in the loaded directory.");
CB_Players.Items.Clear();

View File

@ -69,7 +69,7 @@ private void LoadComboBoxes()
CB_MapAcreSelect.DisplayMember = nameof(ComboItem.Text);
CB_MapAcreSelect.ValueMember = nameof(ComboItem.Value);
CB_MapAcreSelect.DataSource = ComboItemUtil.GetArray<ushort>(typeof(OutsideAcre));
CB_MapAcreSelect.DataSource = ComboItemUtil.GetArray<OutsideAcre>();
NUD_MapAcreTemplateOutside.Value = SAV.OutsideFieldTemplateUniqueId;
NUD_MapAcreTemplateField.Value = SAV.MainFieldParamUniqueID;

View File

@ -5,11 +5,8 @@
namespace NHSE.WinForms;
public class SysBotController
public sealed class SysBotController(InjectionType type)
{
public SysBotController(InjectionType type) => Type = type;
private readonly InjectionType Type;
public readonly SysBot Bot = new();
private readonly Settings Settings = Settings.Default;
@ -42,7 +39,7 @@ public bool Connect(string ip, string port)
public uint GetDefaultOffset()
{
var settings = Settings;
return Type switch
return type switch
{
InjectionType.Generic => settings.SysBotGenericOffset,
InjectionType.Pouch => settings.SysBotPouchOffset,
@ -53,7 +50,7 @@ public uint GetDefaultOffset()
public void SetOffset(uint value)
{
var settings = Settings;
switch (Type)
switch (type)
{
case InjectionType.Generic: settings.SysBotGenericOffset = value; break;
case InjectionType.Pouch: settings.SysBotPouchOffset = value; break;

View File

@ -3,7 +3,7 @@
namespace NHSE.WinForms;
public class USBBotController
public sealed class USBBotController
{
public readonly USBBot Bot = new();
@ -20,10 +20,7 @@ public bool Connect()
}
}
public void Disconnect()
{
Bot.Disconnect();
}
public void Disconnect() => Bot.Disconnect();
//todo: this
//public uint GetDefaultOffset()

View File

@ -3,7 +3,7 @@
namespace NHSE.WinForms;
public class InterpolatingPictureBox : PictureBox
public sealed class InterpolatingPictureBox : PictureBox
{
private readonly InterpolationMode InterpolationMode = InterpolationMode.HighQualityBicubic;