Update to .NET 10 (#683)

This commit is contained in:
Kurt 2026-01-13 16:02:58 -06:00 committed by GitHub
parent 63bd5f643c
commit fa3f0e3cc8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
316 changed files with 31897 additions and 32159 deletions

115
.editorconfig Normal file
View File

@ -0,0 +1,115 @@
root = true
# All Files
[*]
charset = utf-8-bom
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
# XML Project Files
[*.{csproj,slnx,props}]
indent_style = space
indent_size = 2
# Code Files
[*.cs]
trim_trailing_whitespace = true
indent_style = space
indent_size = 4
tab_width = 4
end_of_line = crlf
csharp_prefer_braces = when_multiline:warning
dotnet_diagnostic.IDE0047.severity = none
dotnet_diagnostic.IDE0048.severity = none
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggest
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggest
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggest
dotnet_style_parentheses_in_other_operators = always_for_clarity:suggest
csharp_indent_labels = one_less_than_current
csharp_using_directive_placement = outside_namespace:silent
csharp_prefer_simple_using_statement = true:suggestion
csharp_style_namespace_declarations = block_scoped:silent
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_prefer_top_level_statements = true:silent
csharp_style_prefer_primary_constructors = true:suggestion
csharp_prefer_system_threading_lock = true:suggestion
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
dotnet_diagnostic.WFO1000.severity = none
[*.{cs,vb}]
#### Naming styles ####
# Naming styles
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.capitalization = pascal_case
[*.{cs,vb}]
#### Naming styles ####
# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Naming styles
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
dotnet_style_explicit_tuple_names = true:suggestion
# IDE0130: Namespace does not match folder structure
dotnet_diagnostic.IDE0130.severity = none

View File

@ -11,11 +11,6 @@ Editor de partidas guardadas para Animal Crossing: New Horizons
Permite editar partidas guardadas extraídas del Nintendo Switch.
* Debes extraer las partidas guardadas de la consola tu mismo, este programa no las extrae por ti.
## Véase también
[MyHorizons](https://github.com/Cuyler36/MyHorizons) de [Cuyler36](https://github.com/Cuyler36/)
* Algunas partes del código fueron adaptadas del proyecto MyHorizons de Cuyler36 (arriba)
## Otro
Dirígete a la [Wiki](https://github.com/kwsch/NHSE/wiki) para más información.

View File

@ -10,11 +10,6 @@ NHSE
Modifiez les données de sauvegarde extraites de votre Nintendo Switch.
* Veuillez préparer vos propres données de sauvegarde. Ce programme n'extrait pas les données de sauvegarde de la console.
## Voir également
[MyHorizons](https://github.com/Cuyler36/MyHorizons) par [Cuyler36](https://github.com/Cuyler36/)
* Une partie du code est fortement adaptée du projet de Cuyler36 ci-dessus.
## Autre
Consultez le [Wiki](https://github.com/kwsch/NHSE/wiki) pour plus d'informations.

View File

@ -11,11 +11,6 @@ Modificatore di salvataggi per Animal Crossing: New Horizons
Puoi modificare i salvataggi esportati dalla tua Nintendo Switch.
* Esporta i tuoi salvataggi; Questo programma non può esportare direttamente i salvataggi dalla tua console.
## Guarda Anche
[MyHorizons](https://github.com/Cuyler36/MyHorizons) di [Cuyler36](https://github.com/Cuyler36/)
* Alcune porzioni del codice sono completamente adattate dal progetto di Cuyler36.
## Altro
Per ulteriori informazioni, consultare la nostra [Wiki](https://github.com/kwsch/NHSE/wiki) (in inglese).

View File

@ -11,11 +11,6 @@ NHSE
Nintendo Switch から抽出したセーブデータを編集します。
* 自分のセーブデータを用意してください。このプログラムはコンソールからセーブデータを抽出しません。
## 参考
[MyHorizons](https://github.com/Cuyler36/MyHorizons) by [Cuyler36](https://github.com/Cuyler36/)
* コードの一部は上記の Cuyler36 のプロジェクトから大いに翻案されています。
## その他
詳しくは [Wiki](https://github.com/kwsch/NHSE/wiki) を参照してください。

View File

@ -10,10 +10,6 @@ NHSE
可以编辑你从Nintendo Switch中导出的存档。
* 请自行解决存档导出问题本程序并不能直接从你的Switch中导出存档。
## 参见
[MyHorizons](https://github.com/Cuyler36/MyHorizons) by [Cuyler36](https://github.com/Cuyler36/)
* 代码的某些部分完全改编自Cuyler36的项目。
## 其他

View File

@ -1,7 +1,9 @@
<Project>
<PropertyGroup>
<LangVersion>10</LangVersion>
<LangVersion>14</LangVersion>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<Product>NHSE</Product>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>

View File

@ -1,9 +1,9 @@
using System.Drawing;
namespace NHSE.Core
namespace NHSE.Core;
public static class AcreTileColor
{
public static class AcreTileColor
{
public static readonly byte[] AcreTiles = ResourceUtil.GetBinaryResource("outside.bin");
public static int GetAcreTileColor(ushort acre, int x, int y)
@ -18,5 +18,4 @@ public static int GetAcreTileColor(ushort acre, int x, int y)
var tile = AcreTiles[ofs];
return CollisionUtil.Dict[tile].ToArgb();
}
}
}

View File

@ -1,10 +1,10 @@
using System.Drawing;
using System.Linq;
namespace NHSE.Core
namespace NHSE.Core;
public static class ColorUtil
{
public static class ColorUtil
{
public static Color GetColor(int index)
{
var arr = Colors;
@ -50,5 +50,4 @@ public static Color Blend(Color color, Color backColor, double amount)
byte b = (byte)((color.B * amount) + (backColor.B * (1 - amount)));
return Color.FromArgb(r, g, b);
}
}
}

View File

@ -1,9 +1,9 @@
using System.Drawing;
namespace NHSE.Core
namespace NHSE.Core;
public static class FieldItemColor
{
public static class FieldItemColor
{
public static Color GetItemColor(Item item)
{
if (item.DisplayItemId >= Item.FieldItemMin)
@ -22,19 +22,19 @@ private static Color GetItemColor60000(Item item)
return Color.DarkGreen;
var kind = def.Kind;
if (kind.IsTree())
if (kind.IsTree)
return GetTreeColor(id);
if (kind.IsFlower())
if (kind.IsFlower)
return Color.HotPink;
if (kind.IsWeed())
if (kind.IsWeed)
return Color.DarkOliveGreen;
if (kind.IsFence())
if (kind.IsFence)
return Color.LightCoral;
if (kind == FieldItemKind.UnitIconHole)
return Color.Black;
if (kind.IsBush())
if (kind.IsBush)
return Color.LightGreen;
if (kind.IsStone())
if (kind.IsStone)
return Color.LightGray;
return Color.DarkGreen; // shouldn't reach here, but ok
@ -42,7 +42,7 @@ private static Color GetItemColor60000(Item item)
private static Color GetTreeColor(ushort id)
{
if (0xEC9C <= id && id <= 0xECA0) // money tree
if (id is >= 0xEC9C and <= 0xECA0) // money tree
return Color.Gold;
return id switch
@ -75,5 +75,4 @@ private static Color GetTreeColor(ushort id)
_ => Color.SandyBrown,
};
}
}
}

View File

@ -1,9 +1,9 @@
using System.Drawing;
namespace NHSE.Core
namespace NHSE.Core;
public static class ItemColor
{
public static class ItemColor
{
public static Color GetItemColor(Item item)
{
if (item.ItemId == Item.NONE)
@ -23,5 +23,4 @@ public static Color GetItemColor(ushort item)
return Color.LimeGreen;
return ColorUtil.GetColor((int)kind);
}
}
}

View File

@ -3,16 +3,16 @@
using static NHSE.Core.TerrainUnitModel;
using static NHSE.Core.LandAngles;
namespace NHSE.Core
namespace NHSE.Core;
public static class TerrainTileColor
{
public static class TerrainTileColor
{
private static readonly Color River = Color.FromArgb(128, 215, 195);
private static readonly Color Grass = Color.ForestGreen;
public static Color GetTileColor(TerrainTile tile, int relativeX, int relativeY)
{
if (tile.UnitModelRoad.IsRoad())
if (tile.UnitModelRoad.IsRoad)
return GetRoadColor(tile.UnitModelRoad);
var baseColor = GetTileDefaultColor(tile.UnitModel, tile.LandMakingAngle, relativeX, relativeY);
if (tile.Elevation == 0)
@ -23,19 +23,19 @@ public static Color GetTileColor(TerrainTile tile, int relativeX, int relativeY)
private static Color GetRoadColor(TerrainUnitModel mdl)
{
if (mdl.IsRoadBrick())
if (mdl.IsRoadBrick)
return Color.Firebrick;
if (mdl.IsRoadDarkSoil())
if (mdl.IsRoadDarkSoil)
return Color.SaddleBrown;
if (mdl.IsRoadSoil())
if (mdl.IsRoadSoil)
return Color.Peru;
if (mdl.IsRoadStone())
if (mdl.IsRoadStone)
return Color.DarkGray;
if (mdl.IsRoadPattern())
if (mdl.IsRoadPattern)
return Color.Ivory;
if (mdl.IsRoadTile())
if (mdl.IsRoadTile)
return Color.SteelBlue;
if (mdl.IsRoadSand())
if (mdl.IsRoadSand)
return Color.SandyBrown;
return Color.BurlyWood;
}
@ -205,11 +205,11 @@ private static bool IsPointInTriangle(int px, int py, Coordinate a, Coordinate b
return Math.Abs(areaTotal - (area1 + area2 + area3)) < 0.0001f;
}
private static float GetTriangleArea(Coordinate A, Coordinate B, Coordinate C)
private static float GetTriangleArea(Coordinate a, Coordinate b, Coordinate c)
{
return Math.Abs((A.X * (B.Y - C.Y) +
B.X * (C.Y - A.Y) +
C.X * (A.Y - B.Y)) / 2.0f);
return Math.Abs(((a.X * (b.Y - c.Y)) +
(b.X * (c.Y - a.Y)) +
(c.X * (a.Y - b.Y))) / 2.0f);
}
private readonly record struct Coordinate(int X, int Y);
@ -219,24 +219,21 @@ private static float GetTriangleArea(Coordinate A, Coordinate B, Coordinate C)
private static Color GetTileDefaultColor(TerrainUnitModel mdl, ushort landAngle, int relativeX, int relativeY)
{
var angle = (LandAngles)landAngle;
if (mdl.IsRiver())
if (mdl.IsRiver)
return GetRiverColor(mdl, angle, relativeX, relativeY);
if (mdl.IsFall())
if (mdl.IsFall)
return Color.DeepSkyBlue;
if (mdl.IsCliff())
if (mdl.IsCliff)
return CliffBase;
return Grass;
}
private static readonly char[] Numbers = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
public static string GetTileName(TerrainTile tile)
{
var name = tile.UnitModel.ToString();
var num = name.IndexOfAny(Numbers);
var num = name.IndexOfAnyInRange('0', '9');
if (num < 0)
return name;
return name.Substring(0, num) + Environment.NewLine + name.Substring(num);
}
return name[..num] + Environment.NewLine + name[num..];
}
}

View File

@ -1,11 +1,10 @@
using System.Collections.Generic;
namespace NHSE.Core
namespace NHSE.Core;
public abstract class BatchMutator<T> where T : class
{
public abstract class BatchMutator<T> where T : class
{
protected const string CONST_RAND = "$rand";
public abstract ModifyResult Modify(T item, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications);
}
}

View File

@ -2,20 +2,17 @@
using System.Collections.Generic;
using System.Linq;
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Carries out a batch edit and contains information summarizing the results.
/// </summary>
public abstract class BatchProcessor<T>(BatchMutator<T> Mutator) where T : class
{
/// <summary>
/// Carries out a batch edit and contains information summarizing the results.
/// </summary>
public abstract class BatchProcessor<T> where T : class
{
private int Modified { get; set; }
private int Iterated { get; set; }
private int Failed { get; set; }
protected readonly BatchMutator<T> Mutator;
protected BatchProcessor(BatchMutator<T> mut) => Mutator = mut;
protected abstract bool CanModify(T item);
protected abstract bool Finalize(T item);
@ -62,7 +59,7 @@ public string GetEditorResults(ICollection<StringInstructionSet> sets)
return result;
}
public void Execute(IList<string> lines, IEnumerable<T> data)
public void Execute(ReadOnlySpan<string> lines, IEnumerable<T> data)
{
var sets = StringInstructionSet.GetBatchSets(lines).ToArray();
foreach (var pk in data)
@ -72,9 +69,9 @@ public void Execute(IList<string> lines, IEnumerable<T> data)
}
}
protected abstract void Initialize(StringInstructionSet[] sets);
protected abstract void Initialize(ReadOnlySpan<StringInstructionSet> sets);
public void Process(StringInstructionSet[] sets, IReadOnlyList<T> items)
public void Process(ReadOnlySpan<StringInstructionSet> sets, IReadOnlyList<T> items)
{
Initialize(sets);
foreach (var s in sets)
@ -83,5 +80,4 @@ public void Process(StringInstructionSet[] sets, IReadOnlyList<T> items)
Process(i, s.Filters, s.Instructions);
}
}
}
}

View File

@ -5,11 +5,12 @@
using System.Linq;
using System.Reflection;
namespace NHSE.Core
namespace NHSE.Core;
public class ItemMutator : BatchMutator<Item>
{
public class ItemMutator : BatchMutator<Item>
{
public readonly ItemReflection Reflect = ItemReflection.Default;
private const char CONST_POINTER = '*';
public override ModifyResult Modify(Item item, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications)
{
@ -48,13 +49,13 @@ public override ModifyResult Modify(Item item, IEnumerable<StringInstruction> fi
}
/// <summary>
/// Sets the if the <see cref="Item"/> should be filtered due to the <see cref="StringInstruction"/> provided.
/// Sets if the <see cref="Item"/> should be filtered due to the <see cref="StringInstruction"/> provided.
/// </summary>
/// <param name="cmd">Command Filter</param>
/// <param name="item">Pokémon to check.</param>
/// <param name="props">PropertyInfo cache (optional)</param>
/// <returns>True if filtered, else false.</returns>
private static ModifyResult SetProperty(StringInstruction cmd, Item item, IReadOnlyDictionary<string, PropertyInfo> props)
private static ModifyResult SetProperty(StringInstruction cmd, Item item, Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>> props)
{
if (SetComplexProperty(item, cmd))
return ModifyResult.Modified;
@ -65,8 +66,10 @@ private static ModifyResult SetProperty(StringInstruction cmd, Item item, IReadO
if (!pi.CanWrite)
return ModifyResult.Error;
object val = cmd.Random ? (object)cmd.RandomValue : cmd.PropertyValue;
ReflectUtil.SetValue(pi, item, val);
if (cmd.Random)
ReflectUtil.SetValue(pi, item, cmd.RandomValue);
else
ReflectUtil.SetValue(pi, item, cmd.PropertyValue);
return ModifyResult.Modified;
}
@ -77,7 +80,7 @@ private static bool SetComplexProperty(Item item, StringInstruction cmd)
{
if (!int.TryParse(cmd.PropertyValue, out var val))
return false;
if (val is not 0 or 0xFFFE)
if (val is not (0 or 0xFFFE))
return false;
item.Delete();
return true;
@ -157,7 +160,7 @@ public bool TryGetHasProperty(Type type, string name, [NotNullWhen(true)] out Pr
/// <param name="item">Pokémon to check.</param>
/// <param name="props">PropertyInfo cache (optional)</param>
/// <returns>True if filter matches, else false.</returns>
private static bool IsFilterMatch(StringInstruction cmd, Item item, IReadOnlyDictionary<string, PropertyInfo> props)
private static bool IsFilterMatch(StringInstruction cmd, Item item, Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>> props)
{
return IsPropertyFiltered(cmd, item, props);
}
@ -169,13 +172,20 @@ private static bool IsFilterMatch(StringInstruction cmd, Item item, IReadOnlyDic
/// <param name="item">Pokémon to check.</param>
/// <param name="props">PropertyInfo cache</param>
/// <returns>True if filtered, else false.</returns>
private static bool IsPropertyFiltered(StringInstruction cmd, Item item, IReadOnlyDictionary<string, PropertyInfo> props)
private static bool IsPropertyFiltered(StringInstruction cmd, Item item, Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>> props)
{
if (!props.TryGetValue(cmd.PropertyName, out var pi))
return false;
if (!pi.CanRead)
return false;
return pi.IsValueEqual(item, cmd.PropertyValue) == cmd.Evaluator;
var val = cmd.PropertyValue;
if (val.StartsWith(CONST_POINTER) && props.TryGetValue(val.AsSpan(1), out var opi))
{
var result = opi.GetValue(item) ?? throw new NullReferenceException();
return cmd.Comparer.IsCompareOperator(pi.CompareTo(item, result));
}
return cmd.Comparer.IsCompareOperator(pi.CompareTo(item, val));
}
/// <summary>
@ -192,7 +202,7 @@ public static bool IsFilterMatch(IEnumerable<StringInstruction> filters, object
return false;
try
{
if (pi.IsValueEqual(obj, cmd.PropertyValue) == cmd.Evaluator)
if (cmd.Comparer.IsCompareOperator(pi.CompareTo(obj, cmd.PropertyValue)))
continue;
}
// User provided inputs can mismatch the type's required value format, and fail to be compared.
@ -205,5 +215,4 @@ public static bool IsFilterMatch(IEnumerable<StringInstruction> filters, object
}
return true;
}
}
}

View File

@ -1,14 +1,11 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
namespace NHSE.Core
{
public class ItemProcessor : BatchProcessor<Item>
{
public ItemProcessor(BatchMutator<Item> mut) : base(mut)
{
}
namespace NHSE.Core;
public class ItemProcessor(BatchMutator<Item> mut) : BatchProcessor<Item>(mut)
{
protected override bool CanModify(Item item) => true;
protected override bool Finalize(Item item) => true;
@ -16,13 +13,13 @@ public ItemProcessor(BatchMutator<Item> mut) : base(mut)
/// Initializes the <see cref="StringInstruction"/> list with a context-sensitive value. If the provided value is a string, it will attempt to convert that string to its corresponding index.
/// </summary>
/// <param name="il">Instructions to initialize.</param>
public void ScreenStrings(IEnumerable<StringInstruction> il)
public static void ScreenStrings(IEnumerable<StringInstruction> il)
{
foreach (var i in il.Where(i => !i.PropertyValue.All(char.IsDigit)))
{
string pv = i.PropertyValue;
if (pv.StartsWith("$") && pv.Contains(","))
i.SetRandRange(pv);
if (pv.StartsWith('$') && pv.Contains(','))
i.SetRandomRange(pv);
SetInstructionScreenedValue(i);
}
@ -40,7 +37,7 @@ private static void SetInstructionScreenedValue(StringInstruction i)
}
}
protected override void Initialize(StringInstructionSet[] sets)
protected override void Initialize(ReadOnlySpan<StringInstructionSet> sets)
{
foreach (var set in sets)
{
@ -48,5 +45,4 @@ protected override void Initialize(StringInstructionSet[] sets)
ScreenStrings(set.Instructions);
}
}
}
}

View File

@ -3,48 +3,82 @@
using System.Linq;
using System.Reflection;
namespace NHSE.Core
namespace NHSE.Core;
public class ItemReflection
{
public class ItemReflection
{
public static ItemReflection Default { get; } = new();
public readonly Type[] Types = { typeof(Item), typeof(VillagerItem) };
public readonly Dictionary<string, PropertyInfo>[] Props;
public readonly string[][] Properties;
public readonly Type[] Types = [typeof(Item), typeof(VillagerItem)];
public string[][] Properties => GetProperties.Value;
/// <summary>
/// Extra properties to show in the list of selectable properties (GUI)
/// </summary>
private static readonly string[] CustomProperties =
[
];
public readonly Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>>[] Props;
private readonly Lazy<string[][]> GetProperties;
public ItemReflection()
{
Props = Types
.Select(z => ReflectUtil.GetAllPropertyInfoPublic(z)
.GroupBy(p => p.Name)
.Select(g => g.First())
.ToDictionary(p => p.Name))
.ToArray();
Properties = GetPropArray();
Props = GetPropertyDictionaries(Types);
GetProperties = new Lazy<string[][]>(() => GetPropArray(Props, CustomProperties));
}
public string[][] GetPropArray()
private static Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>>[] GetPropertyDictionaries(ReadOnlySpan<Type> types)
{
var p = new string[Types.Length][];
var result = new Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>>[types.Length];
for (int i = 0; i < types.Length; i++)
result[i] = GetPropertyDictionary(types[i], ReflectUtil.GetAllPropertyInfoPublic).GetAlternateLookup<ReadOnlySpan<char>>();
return result;
}
private static Dictionary<string, PropertyInfo> GetPropertyDictionary(Type type, Func<Type, IEnumerable<PropertyInfo>> selector)
{
const int expectedMax = 8;
var dict = new Dictionary<string, PropertyInfo>(expectedMax);
var props = selector(type);
foreach (var p in props)
dict.TryAdd(p.Name, p);
return dict;
}
private static string[][] GetPropArray<T>(Dictionary<string, T>.AlternateLookup<ReadOnlySpan<char>>[] types, ReadOnlySpan<string> extra)
{
// Create a list for all types, [inAny, ..types, inAll]
var result = new string[types.Length + 2][];
var p = result.AsSpan(1, types.Length);
for (int i = 0; i < p.Length; i++)
{
var pz = ReflectUtil.GetPropertiesPublic(Types[i]);
p[i] = pz.OrderBy(a => a).ToArray();
var type = types[i].Dictionary;
string[] combine = [.. type.Keys, .. extra];
Array.Sort(combine);
p[i] = combine;
}
// Properties for any
var any = ReflectUtil.GetPropertiesPublic(typeof(Item)).Union(p.SelectMany(a => a)).OrderBy(a => a).ToArray();
// Properties shared by all
var all = p.Aggregate(new HashSet<string>(p[0]), (h, e) => { h.IntersectWith(e); return h; }).OrderBy(a => a).ToArray();
var p1 = new string[Types.Length + 2][];
Array.Copy(p, 0, p1, 1, p.Length);
p1[0] = any;
p1[p1.Length - 1] = all;
return p1;
// Properties for any PKM
// Properties shared by all PKM
var first = p[0];
var any = new HashSet<string>(first);
var all = new HashSet<string>(first);
foreach (var set in p[1..])
{
any.UnionWith(set);
all.IntersectWith(set);
}
var arrAny = any.ToArray();
Array.Sort(arrAny);
result[0] = arrAny;
var arrAll = all.ToArray();
Array.Sort(arrAll);
result[^1] = arrAll;
return result;
}
}

View File

@ -1,10 +1,10 @@
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Batch Editor Modification result for an individual item.
/// </summary>
public enum ModifyResult
{
/// <summary>
/// Batch Editor Modification result for an individual item.
/// </summary>
public enum ModifyResult
{
/// <summary>
/// The data has invalid data and is not a suitable candidate for modification.
/// </summary>
@ -24,5 +24,4 @@ public enum ModifyResult
/// The data was modified.
/// </summary>
Modified,
}
}

View File

@ -1,43 +1,61 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using static NHSE.Core.InstructionComparer;
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Batch Editing instruction
/// </summary>
/// <remarks>
/// Can be a filter (skip), or a modification instruction (modify)
/// </remarks>
/// <see cref="FilterNotEqual"/>
/// <see cref="FilterEqual"/>
/// <see cref="Apply"/>
/// <param name="PropertyName">Property to modify.</param>
/// <param name="PropertyValue">Value to set to the property.</param>
/// <param name="Comparer">Filter Comparison Type</param>
public sealed record StringInstruction(string PropertyName, string PropertyValue, InstructionComparer Comparer)
{
public string PropertyValue { get; private set; } = PropertyValue;
/// <summary>
/// Batch Editing instruction
/// Sets the <see cref="PropertyValue"/> to the index of the value in the input <see cref="arr"/>, if it exists.
/// </summary>
/// <remarks>
/// Can be a filter (skip), or a modification instruction (modify)
/// </remarks>
/// <see cref="Exclude"/>
/// <see cref="Require"/>
/// <see cref="Apply"/>
public sealed class StringInstruction
/// <param name="arr">List of values to search for the <see cref="PropertyValue"/>.</param>
/// <returns>True if the value was found and set, false otherwise.</returns>
public bool SetScreenedValue(ReadOnlySpan<string> arr)
{
public string PropertyName { get; }
public string PropertyValue { get; private set; }
public bool Evaluator { get; private init; }
public StringInstruction(string name, string value)
{
PropertyName = name;
PropertyValue = value;
int index = arr.IndexOf(PropertyValue);
if ((uint)index >= arr.Length)
return false;
PropertyValue = index.ToString();
return true;
}
public void SetScreenedValue(string[] arr)
{
int index = Array.IndexOf(arr, PropertyValue);
PropertyValue = index > -1 ? index.ToString() : PropertyValue;
}
/// <summary>
/// Valid prefixes that are recognized for <see cref="InstructionComparer"/> value comparison types.
/// </summary>
public static ReadOnlySpan<char> Prefixes =>
[
Apply,
FilterEqual, FilterNotEqual, FilterGreaterThan, FilterGreaterThanOrEqual, FilterLessThan, FilterLessThanOrEqual,
];
public static readonly IReadOnlyList<char> Prefixes = new[] { Apply, Require, Exclude };
private const char Exclude = '!';
private const char Require = '=';
private const char Apply = '.';
private const char SplitRange = ',';
private const char FilterEqual = '=';
private const char FilterNotEqual = '!';
private const char FilterGreaterThan = '>';
private const char FilterLessThan = '<';
private const char FilterGreaterThanOrEqual = '≥';
private const char FilterLessThanOrEqual = '≤';
/// <summary>
/// Character which divides a property and a value.
/// </summary>
@ -50,20 +68,45 @@ public void SetScreenedValue(string[] arr)
// Extra Functionality
private int RandomMinimum, RandomMaximum;
/// <summary>
/// Apply a <see cref="RandomValue"/> instead of fixed value, based on the <see cref="RandomMinimum"/> and <see cref="RandomMaximum"/> values.
/// </summary>
public bool Random { get; private set; }
/// <summary>
/// Gets a <see cref="Random"/> value, based on the <see cref="RandomMinimum"/> and <see cref="RandomMaximum"/> values.
/// </summary>
public int RandomValue => RandUtil.Rand.Next(RandomMinimum, RandomMaximum + 1);
public void SetRandRange(string pv)
/// <summary>
/// Checks if the input <see cref="str"/> is a valid "random range" specification.
/// </summary>
public static bool IsRandomRange(ReadOnlySpan<char> str)
{
string str = pv.Substring(1);
var split = str.Split(SplitRange);
int.TryParse(split[0], out RandomMinimum);
int.TryParse(split[1], out RandomMaximum);
// Need at least one character on either side of the splitter char.
int index = str.IndexOf(SplitRange);
return index > 0 && index < str.Length - 1;
}
/// <summary>
/// Sets a "random range" specification to the instruction.
/// </summary>
/// <exception cref="ArgumentException">When the splitter is not present.</exception>
public void SetRandomRange(ReadOnlySpan<char> str)
{
var index = str.IndexOf(SplitRange);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(index);
var min = str[..index];
var max = str[(index + 1)..];
_ = int.TryParse(min, out RandomMinimum);
_ = int.TryParse(max, out RandomMaximum);
if (RandomMinimum == RandomMaximum)
{
PropertyValue = RandomMinimum.ToString();
Debug.WriteLine(PropertyName + " randomization range Min/Max same?");
Debug.WriteLine($"{PropertyName} randomization range Min/Max same?");
}
else
{
@ -71,31 +114,262 @@ public void SetRandRange(string pv)
}
}
public static IEnumerable<StringInstruction> GetFilters(IEnumerable<string> lines)
{
var raw = GetRelevantStrings(lines, Exclude, Require);
return from line in raw
let eval = line[0] == Require
let split = line.Substring(1).Split(SplitInstruction)
where split.Length == 2 && !string.IsNullOrWhiteSpace(split[0])
select new StringInstruction(split[0], split[1]) { Evaluator = eval };
}
/// <summary>
/// Gets a list of <see cref="StringInstruction"/>s from the input <see cref="text"/>.
/// </summary>
public static List<StringInstruction> GetFilters(ReadOnlySpan<char> text) => GetFilters(text.EnumerateLines());
public static IEnumerable<StringInstruction> GetInstructions(IEnumerable<string> lines)
/// <summary>
/// Gets a list of <see cref="StringInstruction"/> filters from the input <see cref="lines"/>.
/// </summary>
public static List<StringInstruction> GetFilters(ReadOnlySpan<string> lines)
{
var raw = GetRelevantStrings(lines, Apply).Select(line => line.Substring(1));
return from line in raw
select line.Split(SplitInstruction) into split
where split.Length == 2
select new StringInstruction(split[0], split[1]);
var result = new List<StringInstruction>(lines.Length);
foreach (var line in lines)
{
if (TryParseFilter(line, out var entry))
result.Add(entry);
}
return result;
}
/// <summary>
/// Weeds out invalid lines and only returns those with a valid first character.
/// Gets a list of <see cref="StringInstruction"/> filters from the input <see cref="lines"/>.
/// </summary>
private static IEnumerable<string> GetRelevantStrings(IEnumerable<string> lines, params char[] pieces)
public static List<StringInstruction> GetFilters(SpanLineEnumerator lines)
{
return lines.Where(line => !string.IsNullOrEmpty(line) && pieces.Any(z => z == line[0]));
var result = new List<StringInstruction>();
foreach (var line in lines)
{
if (TryParseFilter(line, out var entry))
result.Add(entry);
}
return result;
}
/// <summary>
/// Gets a list of <see cref="StringInstruction"/> filters from the input <see cref="lines"/>.
/// </summary>
public static List<StringInstruction> GetFilters(IReadOnlyList<string> lines)
{
var result = new List<StringInstruction>(lines.Count);
foreach (var line in lines)
{
if (TryParseFilter(line, out var entry))
result.Add(entry);
}
return result;
}
/// <summary>
/// Gets a list of <see cref="StringInstruction"/> filters from the input <see cref="lines"/>.
/// </summary>
public static List<StringInstruction> GetFilters(IEnumerable<string> lines)
{
var result = new List<StringInstruction>();
foreach (var line in lines)
{
if (TryParseFilter(line, out var entry))
result.Add(entry);
}
return result;
}
/// <summary>
/// Gets a list of <see cref="StringInstruction"/> instructions from the input <see cref="text"/>.
/// </summary>
public static List<StringInstruction> GetInstructions(ReadOnlySpan<char> text) => GetInstructions(text.EnumerateLines());
/// <summary>
/// Gets a list of <see cref="StringInstruction"/> instructions from the input <see cref="lines"/>.
/// </summary>
public static List<StringInstruction> GetInstructions(ReadOnlySpan<string> lines)
{
var result = new List<StringInstruction>(lines.Length);
foreach (var line in lines)
{
if (TryParseInstruction(line, out var entry))
result.Add(entry);
}
return result;
}
/// <summary>
/// Gets a list of <see cref="StringInstruction"/> instructions from the input <see cref="lines"/>.
/// </summary>
public static List<StringInstruction> GetInstructions(SpanLineEnumerator lines)
{
var result = new List<StringInstruction>();
foreach (var line in lines)
{
if (TryParseInstruction(line, out var entry))
result.Add(entry);
}
return result;
}
/// <summary>
/// Gets a list of <see cref="StringInstruction"/> instructions from the input <see cref="lines"/>.
/// </summary>
public static List<StringInstruction> GetInstructions(IReadOnlyList<string> lines)
{
var result = new List<StringInstruction>(lines.Count);
foreach (var line in lines)
{
if (TryParseInstruction(line, out var entry))
result.Add(entry);
}
return result;
}
/// <summary>
/// Gets a list of <see cref="StringInstruction"/> instructions from the input <see cref="lines"/>.
/// </summary>
public static List<StringInstruction> GetInstructions(IEnumerable<string> lines)
{
var result = new List<StringInstruction>();
foreach (var line in lines)
{
if (TryParseInstruction(line, out var entry))
result.Add(entry);
}
return result;
}
/// <summary>
/// Tries to parse a <see cref="StringInstruction"/> filter from the input <see cref="line"/>.
/// </summary>
public static bool TryParseFilter(ReadOnlySpan<char> line, [NotNullWhen(true)] out StringInstruction? entry)
{
entry = null;
if (line.Length is 0)
return false;
var comparer = GetComparer(line[0]);
if (!comparer.IsSupported)
return false;
return TryParseSplitTuple(line[1..], ref entry, comparer);
}
/// <summary>
/// Tries to parse a <see cref="StringInstruction"/> instruction from the input <see cref="line"/>.
/// </summary>
public static bool TryParseInstruction(ReadOnlySpan<char> line, [NotNullWhen(true)] out StringInstruction? entry)
{
entry = null;
if (!line.StartsWith(Apply))
return false;
return TryParseSplitTuple(line[1..], ref entry);
}
/// <summary>
/// Tries to split a <see cref="StringInstruction"/> tuple from the input <see cref="tuple"/>.
/// </summary>
public static bool TryParseSplitTuple(ReadOnlySpan<char> tuple, [NotNullWhen(true)] ref StringInstruction? entry, InstructionComparer eval = default)
{
if (!TryParseSplitTuple(tuple, out var name, out var value))
return false;
entry = new StringInstruction(name.ToString(), value.ToString(), eval);
return true;
}
/// <summary>
/// Tries to split a <see cref="StringInstruction"/> tuple from the input <see cref="tuple"/>.
/// </summary>
public static bool TryParseSplitTuple(ReadOnlySpan<char> tuple, out ReadOnlySpan<char> name, out ReadOnlySpan<char> value)
{
name = default;
value = default;
var splitIndex = tuple.IndexOf(SplitInstruction);
if (splitIndex <= 0)
return false;
name = tuple[..splitIndex];
if (name.IsWhiteSpace())
return false;
value = tuple[(splitIndex + 1)..];
var noExtra = value.IndexOf(SplitInstruction);
return noExtra == -1;
}
/// <summary>
/// Gets the <see cref="InstructionComparer"/> from the input <see cref="opCode"/>.
/// </summary>
public static InstructionComparer GetComparer(char opCode) => opCode switch
{
FilterEqual => IsEqual,
FilterNotEqual => IsNotEqual,
FilterGreaterThan => IsGreaterThan,
FilterLessThan => IsLessThan,
FilterGreaterThanOrEqual => IsGreaterThanOrEqual,
FilterLessThanOrEqual => IsLessThanOrEqual,
_ => None,
};
}
/// <summary>
/// Value comparison type
/// </summary>
public enum InstructionComparer : byte
{
None,
IsEqual,
IsNotEqual,
IsGreaterThan,
IsGreaterThanOrEqual,
IsLessThan,
IsLessThanOrEqual,
}
/// <summary>
/// Extension methods for <see cref="InstructionComparer"/>
/// </summary>
public static class InstructionComparerExtensions
{
extension(InstructionComparer comparer)
{
/// <summary>
/// Indicates if the <see cref="comparer"/> is supported by the logic.
/// </summary>
/// <returns>True if supported, false if unsupported.</returns>
public bool IsSupported => comparer switch
{
IsEqual => true,
IsNotEqual => true,
IsGreaterThan => true,
IsGreaterThanOrEqual => true,
IsLessThan => true,
IsLessThanOrEqual => true,
_ => false,
};
/// <summary>
/// Checks if the compare operator is satisfied by a boolean comparison result.
/// </summary>
/// <param name="compareResult">Result from Equals comparison</param>
/// <returns>True if satisfied</returns>
/// <remarks>Only use this method if the comparison is boolean only. Use the <see cref="IsCompareOperator"/> otherwise.</remarks>
public bool IsCompareEquivalence(bool compareResult) => comparer switch
{
IsEqual => compareResult,
IsNotEqual => !compareResult,
_ => false,
};
/// <summary>
/// Checks if the compare operator is satisfied by the <see cref="IComparable.CompareTo"/> result.
/// </summary>
/// <param name="compareResult">Result from CompareTo</param>
/// <returns>True if satisfied</returns>
public bool IsCompareOperator(int compareResult) => comparer switch
{
IsEqual => compareResult is 0,
IsNotEqual => compareResult is not 0,
IsGreaterThan => compareResult > 0,
IsGreaterThanOrEqual => compareResult >= 0,
IsLessThan => compareResult < 0,
IsLessThanOrEqual => compareResult <= 0,
_ => false,
};
}
}

View File

@ -1,17 +1,25 @@
using System.Collections.Generic;
using System.Linq;
using System;
using System.Collections.Generic;
using System.Text;
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Processes input of strings into a list of valid Filters and Instructions.
/// </summary>
public sealed class StringInstructionSet
{
/// <summary>
/// Processes input of strings into a list of valid Filters and Instructions.
/// Filters to check if the object should be modified.
/// </summary>
public sealed class StringInstructionSet
{
public readonly IReadOnlyList<StringInstruction> Filters;
/// <summary>
/// Instructions to modify the object.
/// </summary>
public readonly IReadOnlyList<StringInstruction> Instructions;
private const string SetSeparator = ";";
private const char SetSeparatorChar = ';';
public StringInstructionSet(IReadOnlyList<StringInstruction> filters, IReadOnlyList<StringInstruction> instructions)
{
@ -19,20 +27,117 @@ public StringInstructionSet(IReadOnlyList<StringInstruction> filters, IReadOnlyL
Instructions = instructions;
}
public StringInstructionSet(ICollection<string> set)
public StringInstructionSet(ReadOnlySpan<char> text)
{
Filters = StringInstruction.GetFilters(set).ToList();
Instructions = StringInstruction.GetInstructions(set).ToList();
var set = text.EnumerateLines();
Filters = StringInstruction.GetFilters(set);
Instructions = StringInstruction.GetInstructions(set);
}
public static IEnumerable<StringInstructionSet> GetBatchSets(IList<string> lines)
public StringInstructionSet(SpanLineEnumerator set)
{
Filters = StringInstruction.GetFilters(set);
Instructions = StringInstruction.GetInstructions(set);
}
public StringInstructionSet(ReadOnlySpan<string> set)
{
Filters = StringInstruction.GetFilters(set);
Instructions = StringInstruction.GetInstructions(set);
}
/// <summary>
/// Gets a list of <see cref="StringInstructionSet"/>s from the input <see cref="lines"/>.
/// </summary>
public static StringInstructionSet[] GetBatchSets(ReadOnlySpan<string> lines)
{
int ctr = 0;
int start = 0;
while (start < lines.Length)
{
var slice = lines[start..];
var count = GetInstructionSetLength(slice);
ctr++;
start += count + 1;
}
var result = new StringInstructionSet[ctr];
ctr = 0;
start = 0;
while (start < lines.Length)
{
var slice = lines[start..];
var count = GetInstructionSetLength(slice);
var set = slice[..count];
result[ctr++] = new StringInstructionSet(set);
start += count + 1;
}
return result;
}
/// <summary>
/// Gets a list of <see cref="StringInstructionSet"/>s from the input <see cref="text"/>.
/// </summary>
public static StringInstructionSet[] GetBatchSets(ReadOnlySpan<char> text)
{
int ctr = 0;
int start = 0;
while (start < text.Length)
{
var slice = text[start..];
var count = GetInstructionSetLength(slice);
ctr++;
start += count + 1;
}
var result = new StringInstructionSet[ctr];
ctr = 0;
start = 0;
while (start < text.Length)
{
var slice = text[start..];
var count = GetInstructionSetLength(slice);
var set = slice[..count];
result[ctr++] = new StringInstructionSet(set);
start += count + 1;
}
return result;
}
/// <summary>
/// Scans through the <see cref="text"/> to count the amount of characters to consume.
/// </summary>
/// <param name="text">Multi line string</param>
/// <returns>Amount of characters comprising a set of instructions</returns>
public static int GetInstructionSetLength(ReadOnlySpan<char> text)
{
int start = 0;
while (start < lines.Count)
while (start < text.Length)
{
var list = lines.Skip(start).TakeWhile(_ => !lines[start++].StartsWith(SetSeparator)).ToList();
yield return new StringInstructionSet(list);
var line = text[start..];
if (line.Length != 0 && line[0] == SetSeparatorChar)
return start;
var next = line.IndexOf('\n');
if (next == -1)
return text.Length;
start += next + 1;
}
return start;
}
/// <summary>
/// Scans through the <see cref="lines"/> to count the amount of valid lines to consume.
/// </summary>
/// <returns>Amount of lines comprising a set of instructions.</returns>
public static int GetInstructionSetLength(ReadOnlySpan<string> lines)
{
int start = 0;
while (start < lines.Length)
{
var line = lines[start++];
if (line.StartsWith(SetSeparatorChar))
return start;
}
return start;
}
}

View File

@ -1,7 +1,7 @@
namespace NHSE.Core
namespace NHSE.Core;
public sealed class FieldItemColumn
{
public sealed class FieldItemColumn
{
/// <summary> X Coordinate within the Field Item Layer </summary>
public readonly int X;
@ -20,5 +20,4 @@ public FieldItemColumn(int x, int y, int offset, byte[] data)
Offset = offset;
Data = data;
}
}
}

View File

@ -1,13 +1,14 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Converts <see cref="Item"/> into columns of writable Item tiles.
/// </summary>
public static class FieldItemDropper
{
/// <summary>
/// Converts <see cref="Item"/> into columns of writable Item tiles.
/// </summary>
public static class FieldItemDropper
{
private const int MapHeight = FieldItemLayer.FieldItemHeight;
private const int MapWidth = FieldItemLayer.FieldItemWidth;
@ -81,7 +82,7 @@ public static IReadOnlyList<FieldItemColumn> InjectItemsAsDropped(int mapX, int
return result;
}
private static byte[] GetColumnRoot(Item[] items)
private static byte[] GetColumnRoot(ReadOnlySpan<Item> items)
{
var col = new Item[items.Length * 2];
for (int i = 0; i < items.Length; i++)
@ -94,7 +95,7 @@ private static byte[] GetColumnRoot(Item[] items)
return Item.SetArray(col);
}
private static byte[] GetColumnExtension(Item[] items)
private static byte[] GetColumnExtension(ReadOnlySpan<Item> items)
{
var col = new Item[items.Length * 2];
for (int i = 0; i < items.Length; i++)
@ -122,5 +123,4 @@ private static Item GetDroppedItem(Item item)
}
private static Item GetExtension(Item item, byte x, byte y) => new(Item.EXTENSION) { ExtensionItemId = item.ItemId, ExtensionX = x, ExtensionY = y };
}
}

View File

@ -1,18 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using static System.Buffers.Binary.BinaryPrimitives;
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Handles operations for parsing the player inventory.
/// </summary>
/// <remarks>
/// Player Inventory is comprised of multiple values, which we won't bother reimplementing as a convertible data structure.
/// <br>Refer to GSavePlayerItemBaggage's ItemBag &amp; ItemPocket in the dumped c-structure schema.</br>
/// </remarks>
public static class PlayerItemSet
{
/// <summary>
/// Handles operations for parsing the player inventory.
/// </summary>
/// <remarks>
/// Player Inventory is comprised of multiple values, which we won't bother reimplementing as a convertible data structure.
/// <br>Refer to GSavePlayerItemBaggage's ItemBag &amp; ItemPocket in the dumped c-structure schema.</br>
/// </remarks>
public static class PlayerItemSet
{
private const int ItemSet_Quantity = 2; // Pouch (Bag) & Pocket.
private const int ItemSet_ItemCount = 20; // 20 items per item set.
private const int ItemSet_ItemSize = Item.SIZE * ItemSet_ItemCount;
@ -37,14 +38,14 @@ public static void GetOffsetLength(uint slot1, out uint offset, out int length)
/// </summary>
/// <param name="data">Raw RAM from the game from the offset read (as per <see cref="GetOffsetLength"/>).</param>
/// <returns>True if valid, false if not valid or corrupt.</returns>
public static bool ValidateItemBinary(byte[] data)
public static bool ValidateItemBinary(ReadOnlySpan<byte> data)
{
// Check the unlocked slot count -- expect 0,10,20
var bagCount = BitConverter.ToUInt32(data, ItemSet_ItemSize);
var bagCount = ReadUInt32LittleEndian(data[ItemSet_ItemSize..]);
if (bagCount > ItemSet_ItemCount || bagCount % 10 != 0) // pouch21-39 count
return false;
var pocketCount = BitConverter.ToUInt32(data, ItemSet_ItemSize + ItemSet_MetaSize + ItemSet_ItemSize);
var pocketCount = ReadUInt32LittleEndian(data[(ItemSet_ItemSize + ItemSet_MetaSize + ItemSet_ItemSize)..]);
if (pocketCount != ItemSet_ItemCount) // pouch0-19 count should be 20.
return false;
@ -60,7 +61,7 @@ public static bool ValidateItemBinary(byte[] data)
return true;
}
private static bool ValidateBindList(byte[] data, int bindStart, ICollection<byte> bound)
private static bool ValidateBindList(ReadOnlySpan<byte> data, int bindStart, ICollection<byte> bound)
{
for (int i = 0; i < ItemSet_ItemCount; i++)
{
@ -82,7 +83,7 @@ private static bool ValidateBindList(byte[] data, int bindStart, ICollection<byt
/// Reads the items present in the player inventory packet and returns the list of items.
/// </summary>
/// <param name="data">Player Inventory packet</param>
public static Item[] ReadPlayerInventory(byte[] data)
public static Item[] ReadPlayerInventory(ReadOnlySpan<byte> data)
{
var items = GetEmptyItemArray(40);
ReadPlayerInventory(data, items);
@ -102,11 +103,11 @@ private static Item[] GetEmptyItemArray(int count)
/// </summary>
/// <param name="data">Player Inventory packet</param>
/// <param name="destination">40 Item array</param>
public static void ReadPlayerInventory(byte[] data, IReadOnlyList<Item> destination)
public static void ReadPlayerInventory(ReadOnlySpan<byte> data, IReadOnlyList<Item> destination)
{
var pocket2 = destination.Take(20).ToArray();
var pocket1 = destination.Skip(20).ToArray();
var p1 = Item.GetArray(data.Slice(0, ItemSet_ItemSize));
var p1 = Item.GetArray(data[..ItemSet_ItemSize]);
var p2 = Item.GetArray(data.Slice(ItemSet_ItemSize + 0x18, ItemSet_ItemSize));
for (int i = 0; i < pocket1.Length; i++)
@ -121,15 +122,14 @@ public static void ReadPlayerInventory(byte[] data, IReadOnlyList<Item> destinat
/// </summary>
/// <param name="data">Player Inventory packet</param>
/// <param name="source">40 Item array</param>
public static void WritePlayerInventory(byte[] data, IReadOnlyList<Item> source)
public static void WritePlayerInventory(Span<byte> data, IReadOnlyList<Item> source)
{
var pocket2 = source.Take(20).ToArray();
var pocket1 = source.Skip(20).ToArray();
var p1 = Item.SetArray(pocket1);
var p2 = Item.SetArray(pocket2);
p1.CopyTo(data, 0);
p2.CopyTo(data, ItemSet_ItemSize + 0x18);
}
p1.CopyTo(data);
p2.CopyTo(data[(ItemSet_ItemSize + 0x18)..]);
}
}

View File

@ -1,10 +1,10 @@
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Interface describing how items should be configured prior to being dropped by the player.
/// </summary>
public interface IConfigItem
{
/// <summary>
/// Interface describing how items should be configured prior to being dropped by the player.
/// </summary>
public interface IConfigItem
{
/// <summary>
/// Checks if the item should have wrapping paper applied.
/// </summary>
@ -19,5 +19,4 @@ public interface IConfigItem
/// Checks if the Drop Compatibility check should be skipped.
/// </summary>
bool SkipDropCheck { get; }
}
}

View File

@ -2,14 +2,15 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using static System.Buffers.Binary.BinaryPrimitives;
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Logic for retrieving <see cref="Item"/> details based off input strings.
/// </summary>
public static class ItemParser
{
/// <summary>
/// Logic for retrieving <see cref="Item"/> details based off input strings.
/// </summary>
public static class ItemParser
{
/// <summary>
/// Invert the recipe dictionary so we can look up recipe IDs from an input item ID.
/// </summary>
@ -17,8 +18,8 @@ public static class ItemParser
RecipeList.Recipes.ToDictionary(z => z.Value, z => z.Key);
// Users can put spaces between item codes, or newlines. Recognize both!
private static readonly string[] SplittersHex = {" ", "\n", "\r\n"};
private static readonly string[] SplittersName = {",", "\n", "\r\n"};
private static readonly string[] SplittersHex = [" ", "\n", "\r\n"];
private static readonly string[] SplittersName = [",", "\n", "\r\n"];
/// <summary>
/// Gets a list of items from the requested hex string(s).
@ -95,7 +96,7 @@ public static IReadOnlyCollection<Item> GetDIYItemsHexCode(IReadOnlyList<string>
/// Gets a list of DIY item cards from the requested list of item name strings.
/// </summary>
/// <remarks>
/// If a item name parse fails or a recipe ID does not exist, exceptions will be thrown.
/// If an item name parse fails or a recipe ID does not exist, exceptions will be thrown.
/// </remarks>
/// <param name="split">List of item names</param>
/// <param name="lang">Language code to parse with. If the first entry in <see cref="split"/> is a language code, it will be used instead of <see cref="lang"/>.</param>
@ -125,7 +126,7 @@ public static IReadOnlyCollection<Item> GetDIYItemsLanguage(IReadOnlyList<string
/// Gets a list of items from the requested list of item name strings.
/// </summary>
/// <remarks>
/// If a item name parse fails or the item ID does not exist as a known item, exceptions will be thrown.
/// If an item name parse fails or the item ID does not exist as a known item, exceptions will be thrown.
/// </remarks>
/// <param name="split">List of item names</param>
/// <param name="config">Item packaging options</param>
@ -190,7 +191,9 @@ private static byte[] GetBytesFromString(string text)
{
if (!ulong.TryParse(text, NumberStyles.AllowHexSpecifier, CultureInfo.CurrentCulture, out var value))
return Item.NONE.ToBytes();
return BitConverter.GetBytes(value);
var bytes = new byte[sizeof(ulong)];
WriteUInt64LittleEndian(bytes, value);
return bytes;
}
private static Item CreateItem(string name, int requestIndex, IConfigItem config, ItemDestination type, string lang = "en")
@ -246,7 +249,7 @@ private static Item FinalizeItem(int requestIndex, IConfigItem config, ItemDesti
private static CompareOptions GetCompareOption(string str) => str.Any(ch => !char.IsLetterOrDigit(ch) && !char.IsWhiteSpace(ch)) ? optIgnoreSymbols & ~CompareOptions.IgnoreSymbols : optIgnoreSymbols;
/// <summary>
/// Gets the first item name-value that contains the <see cref="itemName"/> (case insensitive).
/// Gets the first item name-value that contains the <see cref="itemName"/> (case-insensitive).
/// </summary>
/// <param name="itemName">Requested Item</param>
/// <param name="lang">Game strings language to fetch with</param>
@ -269,7 +272,7 @@ public static Item GetItem(string itemName, string lang = "en")
}
/// <summary>
/// Gets the first item name-value that contains the <see cref="itemName"/> (case insensitive).
/// Gets the first item name-value that contains the <see cref="itemName"/> (case-insensitive).
/// </summary>
/// <param name="itemName">Requested Item</param>
/// <param name="strings">Game strings</param>
@ -282,7 +285,7 @@ public static Item GetItem(string itemName, IReadOnlyList<ComboItem> strings)
}
/// <summary>
/// Gets the first item name-value that contains the <see cref="itemName"/> (case insensitive).
/// Gets the first item name-value that contains the <see cref="itemName"/> (case-insensitive).
/// </summary>
/// <param name="itemName">Requested Item</param>
/// <param name="strings">List of item name-values</param>
@ -312,7 +315,7 @@ private static bool TryGetItem(string itemName, IEnumerable<ComboItem> strings,
}
/// <summary>
/// Gets an enumerable list of item key-value pairs that contain (case insensitive) the requested <see cref="itemName"/>.
/// Gets an enumerable list of item key-value pairs that contain (case-insensitive) the requested <see cref="itemName"/>.
/// </summary>
/// <param name="itemName">Item name</param>
/// <param name="strings">Item names (and their Item ID values)</param>
@ -329,7 +332,7 @@ public static IEnumerable<ComboItem> GetItemsMatching(string itemName, IEnumerab
}
/// <summary>
/// Gets an enumerable list of item key-value pairs that contain (case insensitive) the requested <see cref="itemName"/>.
/// Gets an enumerable list of item key-value pairs that contain (case-insensitive) the requested <see cref="itemName"/>.
/// </summary>
/// <remarks>
/// Orders the items based on the closest match (<see cref="LevenshteinDistance"/>).
@ -358,7 +361,7 @@ public static IEnumerable<ComboItem> GetItemsClosestOrdered(string itemName, IEn
/// <param name="item">Item value</param>
public static string GetItemText(Item item)
{
var value = BitConverter.ToUInt64(item.ToBytesClass(), 0);
var value = ReadUInt64LittleEndian(item.ToBytesClass());
var name = GameInfo.Strings.GetItemName(item.ItemId);
return $"{name}: {value:X16}";
}
@ -373,12 +376,11 @@ public static ushort GetID(string text)
return Item.NONE;
return (ushort)value;
}
}
}
public enum ItemDestination
{
public enum ItemDestination
{
PlayerDropped,
FieldItemDropped,
HeldItem,
}
}

View File

@ -1,9 +1,9 @@
using System;
namespace NHSE.Core
namespace NHSE.Core;
public static class LevenshteinDistance
{
public static class LevenshteinDistance
{
/// <summary>
/// Compute the distance between two strings.
/// http://www.dotnetperls.com/levenshtein
@ -46,5 +46,4 @@ public static int Compute(string s, string t)
// Step 7
return d[n, m];
}
}
}

View File

@ -2,34 +2,41 @@
using System.Collections.Generic;
using System.Security.Cryptography;
namespace NHSE.Core
namespace NHSE.Core;
// The MIT License (MIT)
// Copyright (c) 2014 Hans Wolff
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
public sealed class Aes128CounterMode : SymmetricAlgorithm
{
// The MIT License (MIT)
// Copyright (c) 2014 Hans Wolff
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
public sealed class Aes128CounterMode : SymmetricAlgorithm
{
private readonly byte[] _counter;
private readonly AesManaged _aes = new() {Mode = CipherMode.ECB, Padding = PaddingMode.None};
private readonly Aes _aes = GetAes();
private static Aes GetAes()
{
var result = Aes.Create();
result.Mode = CipherMode.ECB;
result.Padding = PaddingMode.None;
return result;
}
public Aes128CounterMode(byte[] counter)
{
@ -39,16 +46,16 @@ public Aes128CounterMode(byte[] counter)
_counter = counter;
}
public override ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[] ignoredParameter) => new CounterModeCryptoTransform(_aes, rgbKey, _counter);
public override ICryptoTransform CreateDecryptor(byte[] rgbKey, byte[] ignoredParameter) => new CounterModeCryptoTransform(_aes, rgbKey, _counter);
public override ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[]? ignoredParameter) => new CounterModeCryptoTransform(_aes, rgbKey, _counter);
public override ICryptoTransform CreateDecryptor(byte[] rgbKey, byte[]? ignoredParameter) => new CounterModeCryptoTransform(_aes, rgbKey, _counter);
public override void GenerateKey() => _aes.GenerateKey();
public override void GenerateIV() { /* IV not needed in Counter Mode */ }
protected override void Dispose(bool disposing) => _aes.Dispose();
}
}
public sealed class CounterModeCryptoTransform : ICryptoTransform
{
public sealed class CounterModeCryptoTransform : ICryptoTransform
{
private readonly byte[] _counter;
private readonly ICryptoTransform _counterEncryptor;
private readonly Queue<byte> _xorMask = new();
@ -119,5 +126,4 @@ private void IncrementCounter()
public bool CanReuseTransform => false;
public void Dispose() => _counterEncryptor.Dispose();
}
}

View File

@ -1,16 +1,3 @@
namespace NHSE.Core
{
internal readonly ref struct CryptoFile
{
public readonly byte[] Data;
public readonly byte[] Key;
public readonly byte[] Ctr;
namespace NHSE.Core;
public CryptoFile(byte[] data, byte[] key, byte[] ctr)
{
Data = data;
Key = key;
Ctr = ctr;
}
}
}
internal readonly record struct CryptoFile(byte[] Data, byte[] Key, byte[] Ctr);

View File

@ -1,9 +1,10 @@
using System;
using static System.Buffers.Binary.BinaryPrimitives;
namespace NHSE.Core
namespace NHSE.Core;
public sealed class EncryptedInt32
{
public sealed class EncryptedInt32
{
// Encryption constant used to encrypt the int.
private const uint ENCRYPTION_CONSTANT = 0x80E32B11;
// Base shift count used in the encryption.
@ -27,7 +28,8 @@ public EncryptedInt32(uint encryptedValue, ushort adjust = 0, byte shift = 0, by
Value = Decrypt(encryptedValue, shift, adjust);
}
public void Write(byte[] data, int offset) => Write(this, data, offset);
public void Write(Span<byte> data) => Write(this, data);
public void Write(byte[] data, int offset) => Write(data.AsSpan(offset));
// Calculates a checksum for a given encrypted value
// Checksum calculation is every byte of the encrypted in added together minus 0x2D.
@ -47,35 +49,33 @@ public static uint Decrypt(uint encrypted, byte shift, ushort adjust)
public static uint Encrypt(uint value, byte shift, ushort adjust)
{
ulong val = (ulong) (value + (adjust - ENCRYPTION_CONSTANT)) << (shift + SHIFT_BASE);
ulong val = (ulong) (value + unchecked(adjust - ENCRYPTION_CONSTANT)) << (shift + SHIFT_BASE);
return (uint) ((val >> 32) + val);
}
public static EncryptedInt32 ReadVerify(byte[] data, int offset)
public static EncryptedInt32 ReadVerify(ReadOnlySpan<byte> data, int offset)
{
var val = Read(data, offset);
var val = Read(data[offset..]);
if (val.Checksum != CalculateChecksum(val.OriginalEncrypted))
throw new ArgumentException($"Failed to verify the {nameof(EncryptedInt32)} at {nameof(offset)}");
return val;
}
public static EncryptedInt32 Read(byte[] data, int offset)
public static EncryptedInt32 Read(ReadOnlySpan<byte> data)
{
var enc = BitConverter.ToUInt32(data, offset + 0);
var adjust = BitConverter.ToUInt16(data, offset + 4);
var shift = data[offset + 6];
var chk = data[offset + 7];
return new EncryptedInt32(enc, adjust, shift, chk);
var encrypted = ReadUInt32LittleEndian(data);
var adjust = ReadUInt16LittleEndian(data[4..]);
var shift = data[6];
var chk = data[7];
return new EncryptedInt32(encrypted, adjust, shift, chk);
}
public static void Write(EncryptedInt32 value, byte[] data, int offset)
public static void Write(EncryptedInt32 value, Span<byte> data)
{
uint enc = Encrypt(value.Value, value.Shift, value.Adjust);
byte chk = CalculateChecksum(enc);
BitConverter.GetBytes(enc).CopyTo(data, offset + 0);
BitConverter.GetBytes(value.Adjust).CopyTo(data, offset + 4);
data[offset + 6] = value.Shift;
data[offset + 7] = chk;
}
var encrypted = Encrypt(value.Value, value.Shift, value.Adjust);
WriteUInt32LittleEndian(data, encrypted);
WriteUInt16LittleEndian(data[4..], value.Adjust);
data[6] = value.Shift;
data[7] = CalculateChecksum(encrypted);
}
}

View File

@ -1,7 +1,7 @@
namespace NHSE.Core
namespace NHSE.Core;
public readonly ref struct EncryptedSaveFile
{
public readonly ref struct EncryptedSaveFile
{
public readonly byte[] Data;
public readonly byte[] Header;
@ -12,10 +12,9 @@ public EncryptedSaveFile(byte[] data, byte[] header)
}
#region Equality Comparison
public override bool Equals(object obj) => false;
public override bool Equals(object? obj) => false;
public override int GetHashCode() => Data.GetHashCode();
public static bool operator !=(EncryptedSaveFile left, EncryptedSaveFile right) => !(left == right);
public static bool operator ==(EncryptedSaveFile left, EncryptedSaveFile right) => left.Data == right.Data && left.Header == right.Header;
#endregion
}
}

View File

@ -1,13 +1,13 @@
using System;
namespace NHSE.Core
namespace NHSE.Core;
public static class Encryption
{
public static class Encryption
private static byte[] GetParam(ReadOnlySpan<uint> data, in int index)
{
private static byte[] GetParam(uint[] data, in int index)
{
var rand = new XorShift128(data[data[index] & 0x7F]);
var prms = data[data[index + 1] & 0x7F] & 0x7F;
var rand = new XorShift128(data[(int)data[index] & 0x7F]);
var prms = data[(int)(data[index + 1] & 0x7F)] & 0x7F;
var rndRollCount = (prms & 0xF) + 1;
for (var i = 0; i < rndRollCount; i++)
@ -78,5 +78,4 @@ public static EncryptedSaveFile Encrypt(byte[] data, uint seed, byte[] versionDa
return new EncryptedSaveFile(encData, header.Data);
}
}
}

View File

@ -1,12 +1,12 @@
using System.Collections.Generic;
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Contains the <see cref="HashRegions"/> for a <see cref="FileName"/>.
/// </summary>
public sealed class FileHashDetails
{
/// <summary>
/// Contains the <see cref="HashRegions"/> for a <see cref="FileName"/>.
/// </summary>
public sealed class FileHashDetails
{
/// <summary>
/// Name of the File that these <see cref="HashRegions"/> apply to.
/// </summary>
@ -28,5 +28,4 @@ public FileHashDetails(string fileName, uint fileSize, IReadOnlyList<FileHashReg
FileSize = fileSize;
HashRegions = regions;
}
}
}

View File

@ -1,10 +1,10 @@
using System.Collections.Generic;
using System.Linq;
namespace NHSE.Core
namespace NHSE.Core;
public sealed class FileHashInfo
{
public sealed class FileHashInfo
{
private readonly IReadOnlyDictionary<uint, FileHashDetails> List;
public FileHashInfo(FileHashInfo dupe) : this(dupe.List.Values) { }
@ -21,5 +21,4 @@ public FileHashInfo(IEnumerable<FileHashDetails> hashSets)
{
return List.Values.FirstOrDefault(z => z.FileName == nameData);
}
}
}

View File

@ -1,20 +1,12 @@
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Specifies the region that a validation hash is calculated over.
/// </summary>
/// <param name="HashOffset">Offset of the calculated hash.</param>
/// <param name="Size">Length of the hashed data.</param>
public readonly record struct FileHashRegion(int HashOffset, int Size)
{
/// <summary>
/// Specifies the region that a validation hash is calculated over.
/// </summary>
public readonly struct FileHashRegion
{
/// <summary>
/// Offset of the calculated hash.
/// </summary>
public readonly int HashOffset;
/// <summary>
/// Length of the hashed data.
/// </summary>
public readonly int Size;
/// <summary>
/// Offset where the data to be hashed starts at (calculated).
/// </summary>
@ -26,30 +18,4 @@
public int EndOffset => BeginOffset + Size;
public override string ToString() => $"0x{HashOffset:X}: (0x{BeginOffset:X}-0x{EndOffset:X})";
public FileHashRegion(int hashOfs, int size)
{
HashOffset = hashOfs;
Size = size;
}
#region Equality Comparison
public override bool Equals(object obj) => obj is FileHashRegion r && r == this;
// ReSharper disable once PossiblyImpureMethodCallOnReadonlyVariable
public override int GetHashCode() => BeginOffset.GetHashCode();
public static bool operator !=(FileHashRegion left, FileHashRegion right) => !(left == right);
public static bool operator ==(FileHashRegion left, FileHashRegion right)
{
if (left.HashOffset != right.HashOffset)
return false;
if (left.BeginOffset != right.BeginOffset)
return false;
if (left.Size != right.Size)
return false;
return true;
}
#endregion
}
}

View File

@ -1,10 +1,10 @@
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Provides information for hashing different revisions of the game's savedata.
/// </summary>
public static class FileHashRevision
{
/// <summary>
/// Provides information for hashing different revisions of the game's savedata.
/// </summary>
public static class FileHashRevision
{
private const string FN_MAIN = "main.dat";
private const string FN_PERSONAL = "personal.dat";
private const string FN_POSTBOX = "postbox.dat";
@ -20,10 +20,8 @@ public static class FileHashRevision
internal const int REV_100_PHOTO = 0x263B4;
internal const int REV_100_PROFILE = 0x69508;
public static readonly FileHashInfo REV_100 = new(new FileHashDetails[]
{
new(FN_MAIN, REV_100_MAIN, new FileHashRegion[]
{
public static readonly FileHashInfo REV_100 = new([
new(FN_MAIN, REV_100_MAIN, [
new(0x000108, 0x1D6D4C),
new(0x1D6E58, 0x323384),
new(0x4FA2E8, 0x035AC4),
@ -43,25 +41,21 @@ public static class FileHashRevision
new(0x7EC918, 0x035AC4),
new(0x8223E0, 0x03607C),
new(0x858460, 0x2684D4)
}),
new(FN_PERSONAL, REV_100_PERSONAL, new FileHashRegion[]
{
]),
new(FN_PERSONAL, REV_100_PERSONAL, [
new(0x00108, 0x35AC4),
new(0x35BD0, 0x3607C)
}),
new(FN_POSTBOX, REV_100_POSTBOX, new FileHashRegion[]
{
]),
new(FN_POSTBOX, REV_100_POSTBOX, [
new(0x000100, 0xB4447C)
}),
new(FN_PHOTO, REV_100_PHOTO, new FileHashRegion[]
{
]),
new(FN_PHOTO, REV_100_PHOTO, [
new(0x000100, 0x262B0)
}),
new(FN_PROFILE, REV_100_PROFILE, new FileHashRegion[]
{
]),
new(FN_PROFILE, REV_100_PROFILE, [
new(0x000100, 0x69404)
}),
});
])
]);
#endregion
@ -73,10 +67,8 @@ public static class FileHashRevision
internal const int REV_110_PHOTO = 0x263C0;
internal const int REV_110_PROFILE = 0x69560;
public static readonly FileHashInfo REV_110 = new(new FileHashDetails[]
{
new(FN_MAIN, REV_110_MAIN, new FileHashRegion[]
{
public static readonly FileHashInfo REV_110 = new([
new(FN_MAIN, REV_110_MAIN, [
new(0x000110, 0x1D6D5C),
new(0x1D6E70, 0x323C0C),
new(0x4FAB90, 0x035AFC),
@ -96,25 +88,21 @@ public static class FileHashRevision
new(0x7EE340, 0x035AFC),
new(0x823E40, 0x0362BC),
new(0x85A100, 0x26899C)
}),
new(FN_PERSONAL, REV_110_PERSONAL, new FileHashRegion[]
{
]),
new(FN_PERSONAL, REV_110_PERSONAL, [
new(0x00110, 0x35AFC),
new(0x35C10, 0x362BC)
}),
new(FN_POSTBOX, REV_110_POSTBOX, new FileHashRegion[]
{
]),
new(FN_POSTBOX, REV_110_POSTBOX, [
new(0x000100, 0xB4448C)
}),
new(FN_PHOTO, REV_110_PHOTO, new FileHashRegion[]
{
]),
new(FN_PHOTO, REV_110_PHOTO, [
new(0x000100, 0x262BC)
}),
new(FN_PROFILE, REV_110_PROFILE, new FileHashRegion[]
{
]),
new(FN_PROFILE, REV_110_PROFILE, [
new(0x000100, 0x6945C)
}),
});
])
]);
#endregion
@ -126,10 +114,8 @@ public static class FileHashRevision
internal const int REV_120_PHOTO = 0x2C9C0;
internal const int REV_120_PROFILE = REV_110_PROFILE;
public static readonly FileHashInfo REV_120 = new(new FileHashDetails[]
{
new(FN_MAIN, REV_120_MAIN, new FileHashRegion[]
{
public static readonly FileHashInfo REV_120 = new([
new(FN_MAIN, REV_120_MAIN, [
new(0x000110, 0x1D6D5C),
new(0x1D6E70, 0x323EBC),
new(0x4FAE40, 0x035D2C),
@ -149,25 +135,21 @@ public static class FileHashRevision
new(0x7F8D80, 0x035D2C),
new(0x82EAB0, 0x03787C),
new(0x866330, 0x26899C)
}),
new(FN_PERSONAL, REV_120_PERSONAL, new FileHashRegion[]
{
]),
new(FN_PERSONAL, REV_120_PERSONAL, [
new(0x00110, 0x35D2C),
new(0x35E40, 0x3787C)
}),
new(FN_POSTBOX, REV_120_POSTBOX, new FileHashRegion[]
{
]),
new(FN_POSTBOX, REV_120_POSTBOX, [
new(0x000100, 0xB4448C)
}),
new(FN_PHOTO, REV_120_PHOTO, new FileHashRegion[]
{
]),
new(FN_PHOTO, REV_120_PHOTO, [
new(0x000100, 0x2C8BC)
}),
new(FN_PROFILE, REV_120_PROFILE, new FileHashRegion[]
{
]),
new(FN_PROFILE, REV_120_PROFILE, [
new(0x000100, 0x6945C)
}),
});
])
]);
#endregion
@ -179,10 +161,8 @@ public static class FileHashRevision
internal const int REV_130_PHOTO = REV_120_PHOTO;
internal const int REV_130_PROFILE = REV_110_PROFILE;
public static readonly FileHashInfo REV_130 = new(new FileHashDetails[]
{
new(FN_MAIN, REV_130_MAIN, new FileHashRegion[]
{
public static readonly FileHashInfo REV_130 = new([
new(FN_MAIN, REV_130_MAIN, [
new(0x000110, 0x1D6D5C),
new(0x1D6E70, 0x323EEC),
new(0x4FAE70, 0x035D2C),
@ -202,25 +182,21 @@ public static class FileHashRevision
new(0x7F8E20, 0x035D2C),
new(0x82EB50, 0x03788C),
new(0x8663E0, 0x26899C)
}),
new(FN_PERSONAL, REV_130_PERSONAL, new FileHashRegion[]
{
]),
new(FN_PERSONAL, REV_130_PERSONAL, [
new(0x00110, 0x35D2C),
new(0x35E40, 0x3788C)
}),
new(FN_POSTBOX, REV_130_POSTBOX, new FileHashRegion[]
{
]),
new(FN_POSTBOX, REV_130_POSTBOX, [
new(0x000100, 0xB4448C)
}),
new(FN_PHOTO, REV_130_PHOTO, new FileHashRegion[]
{
]),
new(FN_PHOTO, REV_130_PHOTO, [
new(0x000100, 0x2C8BC)
}),
new(FN_PROFILE, REV_130_PROFILE, new FileHashRegion[]
{
]),
new(FN_PROFILE, REV_130_PROFILE, [
new(0x000100, 0x6945C)
}),
});
])
]);
#endregion
@ -232,10 +208,8 @@ public static class FileHashRevision
internal const int REV_140_PHOTO = REV_120_PHOTO;
internal const int REV_140_PROFILE = REV_110_PROFILE;
public static readonly FileHashInfo REV_140 = new(new FileHashDetails[]
{
new(FN_MAIN, REV_140_MAIN, new FileHashRegion[]
{
public static readonly FileHashInfo REV_140 = new([
new(FN_MAIN, REV_140_MAIN, [
new(0x000110, 0x1d6d5c),
new(0x1d6e70, 0x323f2c),
new(0x4faeb0, 0x035d2c),
@ -255,25 +229,21 @@ public static class FileHashRevision
new(0x828b90, 0x035d2c),
new(0x85e8c0, 0x03e5dc),
new(0x89cea0, 0x2688ec)
}),
new(FN_PERSONAL, REV_140_PERSONAL, new FileHashRegion[]
{
]),
new(FN_PERSONAL, REV_140_PERSONAL, [
new(0x00110, 0x35D2C),
new(0x35E40, 0x3E5DC)
}),
new(FN_POSTBOX, REV_140_POSTBOX, new FileHashRegion[]
{
]),
new(FN_POSTBOX, REV_140_POSTBOX, [
new(0x000100, 0xB4448C)
}),
new(FN_PHOTO, REV_140_PHOTO, new FileHashRegion[]
{
]),
new(FN_PHOTO, REV_140_PHOTO, [
new(0x000100, 0x2C8BC)
}),
new(FN_PROFILE, REV_140_PROFILE, new FileHashRegion[]
{
]),
new(FN_PROFILE, REV_140_PROFILE, [
new(0x000100, 0x6945C)
}),
});
])
]);
#endregion
@ -285,10 +255,8 @@ public static class FileHashRevision
internal const int REV_150_PHOTO = REV_120_PHOTO;
internal const int REV_150_PROFILE = REV_110_PROFILE;
public static readonly FileHashInfo REV_150 = new(new FileHashDetails[]
{
new(FN_MAIN, REV_150_MAIN, new FileHashRegion[]
{
public static readonly FileHashInfo REV_150 = new([
new(FN_MAIN, REV_150_MAIN, [
new(0x000110, 0x1e215c),
new(0x1e2270, 0x323f6c),
new(0x5062f0, 0x03693c),
@ -308,25 +276,21 @@ public static class FileHashRevision
new(0x841be0, 0x03693c),
new(0x878520, 0x03f93c),
new(0x8b7e60, 0x2688ec)
}),
new(FN_PERSONAL, REV_150_PERSONAL, new FileHashRegion[]
{
]),
new(FN_PERSONAL, REV_150_PERSONAL, [
new(0x00110, 0x3693c),
new(0x36a50, 0x3f93c)
}),
new(FN_POSTBOX, REV_150_POSTBOX, new FileHashRegion[]
{
]),
new(FN_POSTBOX, REV_150_POSTBOX, [
new(0x000100, 0xB4448C)
}),
new(FN_PHOTO, REV_150_PHOTO, new FileHashRegion[]
{
]),
new(FN_PHOTO, REV_150_PHOTO, [
new(0x000100, 0x2C8BC)
}),
new(FN_PROFILE, REV_150_PROFILE, new FileHashRegion[]
{
]),
new(FN_PROFILE, REV_150_PROFILE, [
new(0x000100, 0x6945C)
}),
});
])
]);
#endregion
@ -338,10 +302,8 @@ public static class FileHashRevision
internal const int REV_160_PHOTO = REV_120_PHOTO;
internal const int REV_160_PROFILE = REV_110_PROFILE;
public static readonly FileHashInfo REV_160 = new(new FileHashDetails[]
{
new(FN_MAIN, REV_160_MAIN, new FileHashRegion[]
{
public static readonly FileHashInfo REV_160 = new([
new(FN_MAIN, REV_160_MAIN, [
new(0x000110, 0x1e215c),
new(0x1e2270, 0x32403c),
new(0x5063c0, 0x03693c),
@ -361,25 +323,21 @@ public static class FileHashRevision
new(0x845e50, 0x03693c),
new(0x87c790, 0x04029c),
new(0x8bca30, 0x268eac)
}),
new(FN_PERSONAL, REV_160_PERSONAL, new FileHashRegion[]
{
]),
new(FN_PERSONAL, REV_160_PERSONAL, [
new(0x00110, 0x3693c),
new(0x36a50, 0x4029c)
}),
new(FN_POSTBOX, REV_160_POSTBOX, new FileHashRegion[]
{
]),
new(FN_POSTBOX, REV_160_POSTBOX, [
new(0x000100, 0xB4448C)
}),
new(FN_PHOTO, REV_160_PHOTO, new FileHashRegion[]
{
]),
new(FN_PHOTO, REV_160_PHOTO, [
new(0x000100, 0x2C8BC)
}),
new(FN_PROFILE, REV_160_PROFILE, new FileHashRegion[]
{
]),
new(FN_PROFILE, REV_160_PROFILE, [
new(0x000100, 0x6945C)
}),
});
])
]);
#endregion
@ -391,10 +349,8 @@ public static class FileHashRevision
internal const int REV_170_PHOTO = REV_120_PHOTO;
internal const int REV_170_PROFILE = REV_110_PROFILE;
public static readonly FileHashInfo REV_170 = new(new FileHashDetails[]
{
new(FN_MAIN, REV_170_MAIN, new FileHashRegion[]
{
public static readonly FileHashInfo REV_170 = new([
new(FN_MAIN, REV_170_MAIN, [
new(0x000110, 0x1e215c),
new(0x1e2270, 0x3221fc),
new(0x504580, 0x03693c),
@ -413,26 +369,22 @@ public static class FileHashRevision
new(0x793640, 0x02d6ec),
new(0x7c0e40, 0x03693c),
new(0x7f7780, 0x02d6ec),
new(0x824e70, 0x024dbc),
}),
new(FN_PERSONAL, REV_170_PERSONAL, new FileHashRegion[]
{
new(0x824e70, 0x024dbc)
]),
new(FN_PERSONAL, REV_170_PERSONAL, [
new(0x00110, 0x3693c),
new(0x36a50, 0x2d6ec),
}),
new(FN_POSTBOX, REV_170_POSTBOX, new FileHashRegion[]
{
new(0x36a50, 0x2d6ec)
]),
new(FN_POSTBOX, REV_170_POSTBOX, [
new(0x000100, 0x4732c)
}),
new(FN_PHOTO, REV_170_PHOTO, new FileHashRegion[]
{
]),
new(FN_PHOTO, REV_170_PHOTO, [
new(0x000100, 0x2C8BC)
}),
new(FN_PROFILE, REV_170_PROFILE, new FileHashRegion[]
{
]),
new(FN_PROFILE, REV_170_PROFILE, [
new(0x000100, 0x6945C)
}),
});
])
]);
#endregion
@ -456,10 +408,8 @@ public static class FileHashRevision
internal const int REV_190_PHOTO = REV_120_PHOTO;
internal const int REV_190_PROFILE = REV_110_PROFILE;
public static readonly FileHashInfo REV_190 = new(new FileHashDetails[]
{
new(FN_MAIN, REV_190_MAIN, new FileHashRegion[]
{
public static readonly FileHashInfo REV_190 = new([
new(FN_MAIN, REV_190_MAIN, [
new(0x000110, 0x1e215c),
new(0x1e2270, 0x34582c),
new(0x527bb0, 0x03693c),
@ -478,26 +428,22 @@ public static class FileHashRevision
new(0x7b6d30, 0x02d70c),
new(0x7e4550, 0x03693c),
new(0x81ae90, 0x02d70c),
new(0x8485a0, 0x024fbc),
}),
new(FN_PERSONAL, REV_190_PERSONAL, new FileHashRegion[]
{
new(0x8485a0, 0x024fbc)
]),
new(FN_PERSONAL, REV_190_PERSONAL, [
new(0x00110, 0x3693c),
new(0x36a50, 0x2d70c),
}),
new(FN_POSTBOX, REV_190_POSTBOX, new FileHashRegion[]
{
new(0x36a50, 0x2d70c)
]),
new(FN_POSTBOX, REV_190_POSTBOX, [
new(0x000100, 0x4732c)
}),
new(FN_PHOTO, REV_190_PHOTO, new FileHashRegion[]
{
]),
new(FN_PHOTO, REV_190_PHOTO, [
new(0x000100, 0x2C8BC)
}),
new(FN_PROFILE, REV_190_PROFILE, new FileHashRegion[]
{
]),
new(FN_PROFILE, REV_190_PROFILE, [
new(0x000100, 0x6945C)
}),
});
])
]);
#endregion
@ -509,10 +455,8 @@ public static class FileHashRevision
internal const int REV_1100_PHOTO = 0x2C9D0;
internal const int REV_1100_PROFILE = REV_110_PROFILE;
public static readonly FileHashInfo REV_1100 = new(new FileHashDetails[]
{
new(FN_MAIN, REV_1100_MAIN, new FileHashRegion[]
{
public static readonly FileHashInfo REV_1100 = new([
new(FN_MAIN, REV_1100_MAIN, [
new(0x000110, 0x1e216c),
new(0x1e2280, 0x34582c),
new(0x527bc0, 0x03693c),
@ -531,26 +475,22 @@ public static class FileHashRevision
new(0x7b6d40, 0x02d70c),
new(0x7e4560, 0x03693c),
new(0x81aea0, 0x02d70c),
new(0x8485b0, 0x024fbc),
}),
new(FN_PERSONAL, REV_1100_PERSONAL, new FileHashRegion[]
{
new(0x8485b0, 0x024fbc)
]),
new(FN_PERSONAL, REV_1100_PERSONAL, [
new(0x00110, 0x3693c),
new(0x36a50, 0x2d70c),
}),
new(FN_POSTBOX, REV_1100_POSTBOX, new FileHashRegion[]
{
new(0x36a50, 0x2d70c)
]),
new(FN_POSTBOX, REV_1100_POSTBOX, [
new(0x000100, 0x4732c)
}),
new(FN_PHOTO, REV_1100_PHOTO, new FileHashRegion[]
{
]),
new(FN_PHOTO, REV_1100_PHOTO, [
new(0x000100, 0x2c8cc)
}),
new(FN_PROFILE, REV_1100_PROFILE, new FileHashRegion[]
{
]),
new(FN_PROFILE, REV_1100_PROFILE, [
new(0x000100, 0x6945C)
}),
});
])
]);
#endregion
@ -575,10 +515,8 @@ public static class FileHashRevision
internal const int REV_200_PROFILE = REV_110_PROFILE;
internal const int REV_200_WHEREAREN = 0xB8A4E0;
public static readonly FileHashInfo REV_200 = new(new FileHashDetails[]
{
new(FN_MAIN, REV_200_MAIN, new FileHashRegion[]
{
public static readonly FileHashInfo REV_200 = new([
new(FN_MAIN, REV_200_MAIN, [
new(0x000110, 0x1e339c),
new(0x1e34b0, 0x36406c),
new(0x547630, 0x03693c),
@ -597,31 +535,25 @@ public static class FileHashRevision
new(0x7fbe30, 0x033acc),
new(0x82fa10, 0x03693c),
new(0x866350, 0x033acc),
new(0x899e20, 0x057d8c),
}),
new(FN_PERSONAL, REV_200_PERSONAL, new FileHashRegion[]
{
new(0x899e20, 0x057d8c)
]),
new(FN_PERSONAL, REV_200_PERSONAL, [
new(0x00110, 0x3693c),
new(0x36a50, 0x33acc),
}),
new(FN_POSTBOX, REV_200_POSTBOX, new FileHashRegion[]
{
new(0x100, 0x4732c),
}),
new(FN_PHOTO, REV_200_PHOTO, new FileHashRegion[]
{
new(0x100, 0x2f54c),
}),
new(FN_PROFILE, REV_200_PROFILE, new FileHashRegion[]
{
new(0x100, 0x6945c),
}),
new(FN_WHEREAREN, REV_200_WHEREAREN, new FileHashRegion[]
{
new(0x100, 0xB8A3DC),
}),
} );
new(0x36a50, 0x33acc)
]),
new(FN_POSTBOX, REV_200_POSTBOX, [
new(0x100, 0x4732c)
]),
new(FN_PHOTO, REV_200_PHOTO, [
new(0x100, 0x2f54c)
]),
new(FN_PROFILE, REV_200_PROFILE, [
new(0x100, 0x6945c)
]),
new(FN_WHEREAREN, REV_200_WHEREAREN, [
new(0x100, 0xB8A3DC)
])
]);
#endregion
}
}

View File

@ -1,12 +1,13 @@
using System;
using static System.Buffers.Binary.BinaryPrimitives;
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// MurmurHash implementation used by Animal Crossing New Horizons
/// </summary>
public static class Murmur3
{
/// <summary>
/// MurmurHash implementation used by Animal Crossing New Horizons
/// </summary>
public static class Murmur3
{
private static uint Murmur32_Scramble(uint k)
{
k = (k * 0x16A88000) | ((k * 0xCC9E2D51) >> 17);
@ -18,35 +19,41 @@ private static uint Murmur32_Scramble(uint k)
/// Updates the hash at the specified offset, using the input parameters.
/// </summary>
/// <param name="data">Data to hash</param>
/// <param name="offset">Where the data-to-be-hashed starts</param>
/// <param name="size">Amount of data to hash</param>
/// <param name="seed">Initial Murmur seed (optional)</param>
/// <returns>Calculated hash.</returns>
public static uint GetMurmur3Hash(byte[] data, int offset, uint size, uint seed = 0)
public static uint GetMurmur3Hash(ReadOnlySpan<byte> data, uint seed = 0)
{
uint checksum = seed;
if (size > 3)
var checksum = seed;
var remaining = data;
while (remaining.Length >= sizeof(uint))
{
for (var i = 0; i < (size / sizeof(uint)); i++)
{
var val = BitConverter.ToUInt32(data, offset);
var val = ReadUInt32LittleEndian(remaining);
checksum ^= Murmur32_Scramble(val);
checksum = (checksum >> 19) | (checksum << 13);
checksum = (checksum * 5) + 0xE6546B64;
offset += 4;
}
remaining = remaining[sizeof(uint)..];
}
var remainder = size % sizeof(uint);
if (remainder != 0)
if (!remaining.IsEmpty)
{
uint val = BitConverter.ToUInt32(data, (int)((offset + size) - remainder));
for (var i = 0; i < (sizeof(uint) - remainder); i++)
val >>= 8;
uint val = 0;
switch (remaining.Length)
{
case 3:
val |= (uint)remaining[2] << 16;
goto case 2;
case 2:
val |= (uint)remaining[1] << 8;
goto case 1;
case 1:
val |= remaining[0];
break;
}
checksum ^= Murmur32_Scramble(val);
}
checksum ^= size;
checksum ^= (uint)data.Length;
checksum ^= checksum >> 16;
checksum *= 0x85EBCA6B;
checksum ^= checksum >> 13;
@ -59,27 +66,12 @@ public static uint GetMurmur3Hash(byte[] data, int offset, uint size, uint seed
/// Updates the hash at the specified offset, using the input parameters.
/// </summary>
/// <param name="data">Data to hash</param>
/// <param name="hashOffset">Offset to write the hash</param>
/// <param name="readOffset">Where the data-to-be-hashed starts</param>
/// <param name="readSize">Amount of data to hash</param>
/// <param name="hashDestination">Location two write the hash</param>
/// <returns>Calculated hash that was written back to the data.</returns>
public static uint UpdateMurmur32(byte[] data, int hashOffset, int readOffset, uint readSize)
public static uint UpdateMurmur32(ReadOnlySpan<byte> data, Span<byte> hashDestination)
{
var newHash = GetMurmur3Hash(data, readOffset, readSize);
var hashBytes = BitConverter.GetBytes(newHash);
hashBytes.CopyTo(data, hashOffset);
var newHash = GetMurmur3Hash(data);
WriteUInt32LittleEndian(hashDestination, newHash);
return newHash;
}
/// <summary>
/// Checks the hash at the specified offset to see if the stored value matches the calculated value.
/// </summary>
/// <param name="data">Data to hash</param>
/// <param name="hashOffset">Offset to write the hash</param>
/// <param name="readOffset">Where the data-to-be-hashed starts</param>
/// <param name="readSize">Amount of data to hash</param>
/// <returns>Calculated hash matches the currently stored hash.</returns>
public static bool VerifyMurmur32(byte[] data, int hashOffset, int readOffset, uint readSize)
=> BitConverter.ToUInt32(data, hashOffset) == GetMurmur3Hash(data, readOffset, readSize);
}
}

View File

@ -1,9 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net46;netstandard2.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\byte\item_kind.bin" />
<None Remove="Resources\byte\item_menuicon.bin" />

View File

@ -1,19 +1,26 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using static System.Buffers.Binary.BinaryPrimitives;
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// main.dat
/// </summary>
public sealed class MainSave : EncryptedFilePair
{
/// <summary>
/// main.dat
/// </summary>
public sealed class MainSave : EncryptedFilePair
{
public readonly MainSaveOffsets Offsets;
public MainSave(string folder) : base(folder, "main") => Offsets = MainSaveOffsets.GetOffsets(Info);
public Hemisphere Hemisphere { get => (Hemisphere)Data[Offsets.WeatherArea]; set => Data[Offsets.WeatherArea] = (byte)value; }
public AirportColor AirportThemeColor { get => (AirportColor)Data[Offsets.AirportThemeColor]; set => Data[Offsets.AirportThemeColor] = (byte)value; }
public uint WeatherSeed { get => BitConverter.ToUInt32(Data, Offsets.WeatherRandSeed); set => BitConverter.GetBytes(value).CopyTo(Data, Offsets.WeatherRandSeed); }
public uint WeatherSeed
{
get => ReadUInt32LittleEndian(Data[Offsets.WeatherRandSeed..]);
set => WriteUInt32LittleEndian(Data[Offsets.WeatherRandSeed..], value);
}
public IVillager GetVillager(int index) => Offsets.ReadVillager(Data, index);
public void SetVillager(IVillager value, int index) => Offsets.WriteVillager(value, Data, index);
@ -50,20 +57,20 @@ public void SetVillagerHouses(IReadOnlyList<IVillagerHouse> villagers)
}
public DesignPattern GetDesign(int index) => Offsets.ReadPattern(Data, index);
public void SetDesign(DesignPattern value, int index, byte[] playerID, byte[] townID) => Offsets.WritePattern(value, Data, index, playerID, townID);
public void SetDesign(DesignPattern value, int index, Span<byte> playerID, Span<byte> townID) => Offsets.WritePattern(value, Data, index, playerID, townID);
public DesignPatternPRO GetDesignPRO(int index) => Offsets.ReadPatternPRO(Data, index);
public void SetDesignPRO(DesignPatternPRO value, int index, byte[] playerID, byte[] townID) => Offsets.WritePatternPRO(value, Data, index, playerID, townID);
public void SetDesignPRO(DesignPatternPRO value, int index, Span<byte> playerID, Span<byte> townID) => Offsets.WritePatternPRO(value, Data, index, playerID, townID);
public IReadOnlyList<Item> RecycleBin
{
get => Item.GetArray(Data.Slice(Offsets.LostItemBox, MainSaveOffsets.RecycleBinCount * Item.SIZE));
set => Item.SetArray(value).CopyTo(Data, Offsets.LostItemBox);
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);
set => Building.SetArray(value).CopyTo(Data[Offsets.MainFieldStructure..]);
}
public IPlayerHouse GetPlayerHouse(int index) => Offsets.ReadPlayerHouse(Data, index);
@ -91,7 +98,7 @@ public DesignPattern[] GetDesigns()
return result;
}
public void SetDesigns(IReadOnlyList<DesignPattern> value, byte[] playerID, byte[] townID)
public void SetDesigns(IReadOnlyList<DesignPattern> value, Span<byte> playerID, Span<byte> townID)
{
var count = Math.Min(Offsets.PatternCount, value.Count);
for (int i = 0; i < count; i++)
@ -106,7 +113,7 @@ public DesignPatternPRO[] GetDesignsPRO()
return result;
}
public void SetDesignsPRO(IReadOnlyList<DesignPatternPRO> value, byte[] playerID, byte[] townID)
public void SetDesignsPRO(IReadOnlyList<DesignPatternPRO> value, Span<byte> playerID, Span<byte> townID)
{
var count = Math.Min(Offsets.PatternCount, value.Count);
for (int i = 0; i < count; i++)
@ -116,7 +123,7 @@ public void SetDesignsPRO(IReadOnlyList<DesignPatternPRO> value, byte[] playerID
public DesignPattern FlagMyDesign
{
get => MainSaveOffsets.ReadPatternAtOffset(Data, Offsets.PatternFlag);
set => value.Data.CopyTo(Data, Offsets.PatternFlag);
set => value.Data.CopyTo(Data[Offsets.PatternFlag..]);
}
public DesignPatternPRO[] GetDesignsTailor()
@ -131,33 +138,23 @@ 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));
value[i].Data.CopyTo(Data.Slice(Offsets.PatternTailor + (i * DesignPatternPRO.SIZE)));
}
private const int EventFlagsSaveCount = 0x400;
public short[] GetEventFlagLand()
{
var value = new short[EventFlagsSaveCount];
Buffer.BlockCopy(Data, Offsets.EventFlagLand, value, 0, sizeof(short) * value.Length);
return value;
}
public void SetEventFlagLand(short[] value)
{
Buffer.BlockCopy(value, 0, Data, Offsets.EventFlagLand, sizeof(short) * value.Length);
}
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).ToClass<TurnipStonk>();
set => value.ToBytesClass().CopyTo(Data, Offsets.ShopKabu);
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));
set => value.Data.CopyTo(Data, Offsets.Museum);
get => new(Data.Slice(Offsets.Museum, Museum.SIZE).ToArray());
set => value.Data.CopyTo(Data[Offsets.Museum..]);
}
public const int AcreWidth = 7 + (2 * 1); // 1 on each side cannot be traversed
@ -169,40 +166,40 @@ public ushort GetAcre(int index)
{
if ((uint)index > AcreMax)
throw new ArgumentOutOfRangeException(nameof(index));
return BitConverter.ToUInt16(Data, Offsets.OutsideField + (index * 2));
return ReadUInt16LittleEndian(Data.Slice(Offsets.OutsideField + (index * 2)));
}
public void SetAcre(int index, ushort value)
{
if ((uint)index > AcreMax)
throw new ArgumentOutOfRangeException(nameof(index));
BitConverter.GetBytes(value).CopyTo(Data, Offsets.OutsideField + (index * 2));
WriteUInt16LittleEndian(Data.Slice(Offsets.OutsideField + (index * 2)), value);
}
public byte[] GetAcreBytes() => Data.Slice(Offsets.OutsideField, AcreSizeAll);
public byte[] GetAcreBytes() => Data.Slice(Offsets.OutsideField, AcreSizeAll).ToArray();
public void SetAcreBytes(byte[] data)
public void SetAcreBytes(ReadOnlySpan<byte> data)
{
if (data.Length != AcreSizeAll)
throw new ArgumentOutOfRangeException(nameof(data.Length));
data.CopyTo(Data, Offsets.OutsideField);
data.CopyTo(Data.Slice(Offsets.OutsideField));
}
public TerrainTile[] GetTerrainTiles() => TerrainTile.GetArray(Data.Slice(Offsets.LandMakingMap, MapGrid.MapTileCount16x16 * TerrainTile.SIZE));
public void SetTerrainTiles(IReadOnlyList<TerrainTile> array) => TerrainTile.SetArray(array).CopyTo(Data, Offsets.LandMakingMap);
public void SetTerrainTiles(IReadOnlyList<TerrainTile> array) => TerrainTile.SetArray(array).CopyTo(Data[Offsets.LandMakingMap..]);
public const int MapDesignNone = 0xF800;
public ushort[] GetMapDesignTiles()
{
var value = new ushort[112*96];
Buffer.BlockCopy(Data, Offsets.MyDesignMap, value, 0, sizeof(ushort) * value.Length);
return value;
var slice = Data.Slice(Offsets.MyDesignMap, 112 * 96 * sizeof(ushort));
return MemoryMarshal.Cast<byte, ushort>(slice).ToArray();
}
public void SetMapDesignTiles(ushort[] value)
public void SetMapDesignTiles(ReadOnlySpan<ushort> value)
{
Buffer.BlockCopy(value, 0, Data, Offsets.MyDesignMap, sizeof(ushort) * value.Length);
var cast = MemoryMarshal.Cast<ushort, byte>(value);
cast.CopyTo(Data[Offsets.MyDesignMap..]);
}
private const int FieldItemLayerSize = MapGrid.MapTileCount32x32 * Item.SIZE;
@ -214,45 +211,45 @@ public void SetMapDesignTiles(ushort[] value)
public int FieldItemFlag2 => Offsets.FieldItem + (FieldItemLayerSize * 2) + FieldItemFlagSize;
public Item[] GetFieldItemLayer1() => Item.GetArray(Data.Slice(FieldItemLayer1, FieldItemLayerSize));
public void SetFieldItemLayer1(IReadOnlyList<Item> array) => Item.SetArray(array).CopyTo(Data, FieldItemLayer1);
public void SetFieldItemLayer1(IReadOnlyList<Item> array) => Item.SetArray(array).CopyTo(Data[FieldItemLayer1..]);
public Item[] GetFieldItemLayer2() => Item.GetArray(Data.Slice(FieldItemLayer2, FieldItemLayerSize));
public void SetFieldItemLayer2(IReadOnlyList<Item> array) => Item.SetArray(array).CopyTo(Data, FieldItemLayer2);
public void SetFieldItemLayer2(IReadOnlyList<Item> array) => Item.SetArray(array).CopyTo(Data[FieldItemLayer2..]);
public ushort OutsideFieldTemplateUniqueId
{
get => BitConverter.ToUInt16(Data, Offsets.OutsideField + AcreSizeAll);
set => BitConverter.GetBytes(value).CopyTo(Data, Offsets.OutsideField + AcreSizeAll);
get => ReadUInt16LittleEndian(Data[(Offsets.OutsideField + AcreSizeAll)..]);
set => WriteUInt16LittleEndian(Data[(Offsets.OutsideField + AcreSizeAll)..], value);
}
public ushort MainFieldParamUniqueID
{
get => BitConverter.ToUInt16(Data, Offsets.OutsideField + AcreSizeAll + 2);
set => BitConverter.GetBytes(value).CopyTo(Data, Offsets.OutsideField + AcreSizeAll + 2);
get => ReadUInt16LittleEndian(Data.Slice(Offsets.OutsideField + AcreSizeAll + 2));
set => WriteUInt16LittleEndian(Data.Slice(Offsets.OutsideField + AcreSizeAll + 2), value);
}
public uint EventPlazaLeftUpX
{
get => BitConverter.ToUInt32(Data, Offsets.OutsideField + AcreSizeAll + 4);
set => BitConverter.GetBytes(value).CopyTo(Data, Offsets.OutsideField + AcreSizeAll + 4);
get => ReadUInt32LittleEndian(Data.Slice(Offsets.OutsideField + AcreSizeAll + 4));
set => WriteUInt32LittleEndian(Data.Slice(Offsets.OutsideField + AcreSizeAll + 4), value);
}
public uint EventPlazaLeftUpZ
{
get => BitConverter.ToUInt32(Data, Offsets.OutsideField + AcreSizeAll + 8);
set => BitConverter.GetBytes(value).CopyTo(Data, Offsets.OutsideField + AcreSizeAll + 8);
get => ReadUInt32LittleEndian(Data.Slice(Offsets.OutsideField + AcreSizeAll + 8));
set => WriteUInt32LittleEndian(Data.Slice(Offsets.OutsideField + AcreSizeAll + 8), value);
}
public GSaveVisitorNpc Visitor
{
get => Data.ToClass<GSaveVisitorNpc>(Offsets.Visitor, GSaveVisitorNpc.SIZE);
set => value.ToBytesClass().CopyTo(Data, Offsets.Visitor);
get => Data.Slice(Offsets.Visitor, GSaveVisitorNpc.SIZE).ToArray().ToClass<GSaveVisitorNpc>();
set => value.ToBytesClass().CopyTo(Data[Offsets.Visitor..]);
}
public GSaveFg SaveFg
{
get => Data.ToClass<GSaveFg>(Offsets.SaveFg, GSaveFg.SIZE);
set => value.ToBytesClass().CopyTo(Data, Offsets.SaveFg);
get => Data.Slice(Offsets.SaveFg, GSaveFg.SIZE).ToArray().ToClass<GSaveFg>();
set => value.ToBytesClass().CopyTo(Data[Offsets.SaveFg..]);
}
public GSaveTime LastSaved => Data.Slice(Offsets.LastSavedTime, GSaveTime.SIZE).ToStructure<GSaveTime>();
@ -260,7 +257,6 @@ public GSaveFg SaveFg
public GSaveBulletinBoard Bulletin
{
get => Data.Slice(Offsets.BulletinBoard, GSaveBulletinBoard.SIZE).ToStructure<GSaveBulletinBoard>();
set => value.ToBytes().CopyTo(Data, Offsets.BulletinBoard);
}
set => value.ToBytes().CopyTo(Data[Offsets.BulletinBoard..]);
}
}

View File

@ -1,103 +1,105 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using static System.Buffers.Binary.BinaryPrimitives;
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// personal.dat
/// </summary>
public sealed class Personal : EncryptedFilePair, IVillagerOrigin
{
/// <summary>
/// personal.dat
/// </summary>
public sealed class Personal : EncryptedFilePair, IVillagerOrigin
{
public readonly PersonalOffsets Offsets;
public Personal(string folder) : base(folder, "personal") => Offsets = PersonalOffsets.GetOffsets(Info);
public override string ToString() => PlayerName;
public uint TownID
{
get => BitConverter.ToUInt32(Data, Offsets.PersonalId + 0x00);
set => BitConverter.GetBytes(value).CopyTo(Data, Offsets.PersonalId + 0x00);
get => ReadUInt32LittleEndian(Data[(Offsets.PersonalId + 0x00)..]);
set => WriteUInt32LittleEndian(Data[(Offsets.PersonalId + 0x00)..], value);
}
public string TownName
{
get => GetString(Offsets.PersonalId + 0x04, 10);
set => GetBytes(value, 10).CopyTo(Data, Offsets.PersonalId + 0x04);
set => GetBytes(value, 10).CopyTo(Data[(Offsets.PersonalId + 0x04)..]);
}
public byte[] GetTownIdentity() => Data.Slice(Offsets.PersonalId + 0x00, 4 + 20);
public Span<byte> GetTownIdentity() => Data.Slice(Offsets.PersonalId + 0x00, 4 + 20);
public uint PlayerID
{
get => BitConverter.ToUInt32(Data, Offsets.PersonalId + 0x1C);
set => BitConverter.GetBytes(value).CopyTo(Data, Offsets.PersonalId + 0x1C);
get => ReadUInt32LittleEndian(Data[(Offsets.PersonalId + 0x1C)..]);
set => WriteUInt32LittleEndian(Data[(Offsets.PersonalId + 0x1C)..], value);
}
public string PlayerName
{
get => GetString(Offsets.PersonalId + 0x20, 10);
set => GetBytes(value, 10).CopyTo(Data, Offsets.PersonalId + 0x20);
set => GetBytes(value, 10).CopyTo(Data[(Offsets.PersonalId + 0x20)..]);
}
public byte[] GetPlayerIdentity() => Data.Slice(Offsets.PersonalId + 0x1C, 4 + 20);
public Span<byte> GetPlayerIdentity() => Data.Slice(Offsets.PersonalId + 0x1C, 4 + 20);
public EncryptedInt32 Wallet
{
get => EncryptedInt32.ReadVerify(Data, Offsets.Wallet);
set => value.Write(Data, Offsets.Wallet);
set => value.Write(Data[Offsets.Wallet..]);
}
public EncryptedInt32 Bank
{
get => EncryptedInt32.ReadVerify(Data, Offsets.Bank);
set => value.Write(Data, Offsets.Bank);
set => value.Write(Data[Offsets.Bank..]);
}
public EncryptedInt32 NookMiles
{
get => EncryptedInt32.ReadVerify(Data, Offsets.NowPoint);
set => value.Write(Data, Offsets.NowPoint);
set => value.Write(Data[Offsets.NowPoint..]);
}
public EncryptedInt32 TotalNookMiles
{
get => EncryptedInt32.ReadVerify(Data, Offsets.TotalPoint);
set => value.Write(Data, Offsets.TotalPoint);
set => value.Write(Data[Offsets.TotalPoint..]);
}
public IReadOnlyList<Item> Bag // Slots 21-40
{
get => Item.GetArray(Data.Slice(Offsets.Pockets1, Offsets.Pockets1Count * Item.SIZE));
set => Item.SetArray(value).CopyTo(Data, Offsets.Pockets1);
set => Item.SetArray(value).CopyTo(Data[Offsets.Pockets1..]);
}
public uint BagCount // Count of the Bag Slots that are available for use
{
get => BitConverter.ToUInt32(Data, Offsets.Pockets1 + (Offsets.Pockets1Count * Item.SIZE));
set => BitConverter.GetBytes(value).CopyTo(Data, Offsets.Pockets1 + (Offsets.Pockets1Count * Item.SIZE));
get => ReadUInt32LittleEndian(Data[(Offsets.Pockets1 + (Offsets.Pockets1Count * Item.SIZE))..]);
set => WriteUInt32LittleEndian(Data[(Offsets.Pockets1 + (Offsets.Pockets1Count * Item.SIZE))..], value);
}
public IReadOnlyList<Item> Pocket // Slots 1-20
{
get => Item.GetArray(Data.Slice(Offsets.Pockets2, Offsets.Pockets2Count * Item.SIZE));
set => Item.SetArray(value).CopyTo(Data, Offsets.Pockets2);
set => Item.SetArray(value).CopyTo(Data[Offsets.Pockets2..]);
}
public uint PocketCount // Count of the Pocket Slots that are available for use
{
get => BitConverter.ToUInt32(Data, Offsets.Pockets2 + (Offsets.Pockets2Count * Item.SIZE));
set => BitConverter.GetBytes(value).CopyTo(Data, Offsets.Pockets2 + (Offsets.Pockets2Count * Item.SIZE));
get => ReadUInt32LittleEndian(Data[(Offsets.Pockets2 + (Offsets.Pockets2Count * Item.SIZE))..]);
set => WriteUInt32LittleEndian(Data[(Offsets.Pockets2 + (Offsets.Pockets2Count * Item.SIZE))..], value);
}
public IReadOnlyList<Item> ItemChest
{
get => Item.GetArray(Data.Slice(Offsets.ItemChest, Offsets.ItemChestCount * Item.SIZE));
set => Item.SetArray(value).CopyTo(Data, Offsets.ItemChest);
set => Item.SetArray(value).CopyTo(Data[Offsets.ItemChest..]);
}
public uint ItemChestCount // Count of the Item Chest Slots that are available for use
{
get => BitConverter.ToUInt32(Data, Offsets.ItemChest + (Offsets.ItemChestCount * Item.SIZE));
set => BitConverter.GetBytes(value).CopyTo(Data, Offsets.ItemChest + (Offsets.ItemChestCount * Item.SIZE));
get => ReadUInt32LittleEndian(Data[(Offsets.ItemChest + (Offsets.ItemChestCount * Item.SIZE))..]);
set => WriteUInt32LittleEndian(Data[(Offsets.ItemChest + (Offsets.ItemChestCount * Item.SIZE))..], value);
}
public IReactionStore Reactions
@ -109,25 +111,29 @@ public IReactionStore Reactions
public AchievementList Achievements
{
get => Data.Slice(Offsets.CountAchievement, AchievementList.SIZE).ToStructure<AchievementList>();
set => value.ToBytes().CopyTo(Data, Offsets.CountAchievement);
set => value.ToBytes().CopyTo(Data[Offsets.CountAchievement..]);
}
public RecipeBook GetRecipeBook() => new(Data.Slice(Offsets.Recipes, RecipeBook.SIZE));
public void SetRecipeBook(RecipeBook book) => book.Save(Data, Offsets.Recipes);
public RecipeBook GetRecipeBook() => new(Data.Slice(Offsets.Recipes, RecipeBook.SIZE).ToArray());
public void SetRecipeBook(RecipeBook book) => book.Save(Data[Offsets.Recipes..]);
public short[] GetEventFlagsPlayer()
{
var result = new short[0xE00/2];
Buffer.BlockCopy(Data, Offsets.EventFlagsPlayer, result, 0, result.Length * sizeof(short));
return result;
var slice = Data.Slice(Offsets.EventFlagsPlayer, 0xE00);
return MemoryMarshal.Cast<byte, short>(slice).ToArray();
}
public void SetEventFlagsPlayer(short[] value) => Buffer.BlockCopy(value, 0, Data, Offsets.EventFlagsPlayer, value.Length * sizeof(short));
public void SetEventFlagsPlayer(Span<short> value)
{
var slice = Data.Slice(Offsets.EventFlagsPlayer, 0xE00);
var cast = MemoryMarshal.Cast<byte, short>(slice);
value.CopyTo(cast);
}
public GSaveDateMD Birthday
{
get => Data.ToStructure<GSaveDateMD>(Offsets.Birthday, GSaveDateMD.SIZE);
set => value.ToBytes().CopyTo(Data, Offsets.Birthday);
set => value.ToBytes().CopyTo(Data[Offsets.Birthday..]);
}
#region Profile
@ -137,28 +143,28 @@ public byte[] GetPhotoData()
var offset = Offsets.ProfilePhoto;
// Expect jpeg marker
if (BitConverter.ToUInt16(Data, offset) != 0xD8FF)
return Array.Empty<byte>();
var len = BitConverter.ToInt32(Data, offset - 4);
return Data.Slice(offset, len);
if (ReadUInt16LittleEndian(Data[offset..]) != 0xD8FF)
return [];
var len = ReadInt32LittleEndian(Data[(offset - 4)..]);
return Data.Slice(offset, len).ToArray();
}
public GSaveDateMD ProfileBirthday
{
get => Data.ToStructure<GSaveDateMD>(Offsets.ProfileBirthday, GSaveDateMD.SIZE);
set => value.ToBytes().CopyTo(Data, Offsets.ProfileBirthday);
set => value.ToBytes().CopyTo(Data[Offsets.ProfileBirthday..]);
}
public ushort ProfileFruit
{
get => BitConverter.ToUInt16(Data, Offsets.ProfileFruit);
set => BitConverter.GetBytes(value).CopyTo(Data, Offsets.ProfileFruit);
get => ReadUInt16LittleEndian(Data[Offsets.ProfileFruit..]);
set => WriteUInt16LittleEndian(Data[Offsets.ProfileFruit..], value);
}
public GSaveDate ProfileTimestamp
{
get => Data.ToStructure<GSaveDate>(Offsets.ProfileTimestamp, GSaveDate.SIZE);
set => value.ToBytes().CopyTo(Data, Offsets.ProfileTimestamp);
set => value.ToBytes().CopyTo(Data[Offsets.ProfileTimestamp..]);
}
public bool ProfileIsMakeVillage
@ -168,5 +174,4 @@ public bool ProfileIsMakeVillage
}
#endregion
}
}

View File

@ -1,10 +1,9 @@
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// photo_studio_island.dat
/// </summary>
public sealed class PhotoStudioIsland : EncryptedFilePair
{
/// <summary>
/// photo_studio_island.dat
/// </summary>
public sealed class PhotoStudioIsland : EncryptedFilePair
{
public PhotoStudioIsland(string folder) : base(folder, "photo_studio_island") { }
}
}

View File

@ -1,10 +1,9 @@
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// postbox.dat
/// </summary>
public sealed class PostBox : EncryptedFilePair
{
/// <summary>
/// postbox.dat
/// </summary>
public sealed class PostBox : EncryptedFilePair
{
public PostBox(string folder) : base(folder, "postbox") { }
}
}

View File

@ -1,12 +1,11 @@
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// profile.dat
/// </summary>
public sealed class Profile : EncryptedFilePair
{
/// <summary>
/// profile.dat
/// </summary>
public sealed class Profile : EncryptedFilePair
{
public Profile(string folder) : base(folder, "profile") { }
// pretty much just a jpeg -- which is also stored in Personal.
}
}

View File

@ -1,9 +1,9 @@
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// </summary>
public sealed class WhereAreN : EncryptedFilePair
{
/// <summary>
/// </summary>
public sealed class WhereAreN : EncryptedFilePair
{
public const string FileName = "wherearen";
public readonly WhereAreNOffsets Offsets;
@ -12,7 +12,6 @@ public sealed class WhereAreN : EncryptedFilePair
public EncryptedInt32 Poki
{
get => EncryptedInt32.ReadVerify(Data, Offsets.Poki);
set => value.Write(Data, Offsets.Poki);
}
set => value.Write(Data[Offsets.Poki..]);
}
}

View File

@ -1,16 +1,20 @@
using System;
using System.Collections.Generic;
using System.IO;
using static System.Buffers.Binary.BinaryPrimitives;
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Represents two files -- <see cref="DataPath"/> and <see cref="HeaderPath"/> and their decrypted data.
/// </summary>
public abstract class EncryptedFilePair
{
/// <summary>
/// Represents two files -- <see cref="DataPath"/> and <see cref="HeaderPath"/> and their decrypted data.
/// </summary>
public abstract class EncryptedFilePair
{
public readonly byte[] Data;
public readonly byte[] Header;
private readonly byte[] RawData;
private readonly byte[] RawHeader;
public Span<byte> Data => RawData;
public Span<byte> Header => RawHeader;
public readonly FileHeaderInfo Info;
@ -40,17 +44,17 @@ protected EncryptedFilePair(string folder, string name)
Encryption.Decrypt(hd, md);
Header = hd;
Data = md;
RawHeader = hd;
RawData = md;
DataPath = dat;
HeaderPath = hdr;
Info = Header.Slice(0, FileHeaderInfo.SIZE).ToClass<FileHeaderInfo>();
Info = RawHeader[..FileHeaderInfo.SIZE].ToClass<FileHeaderInfo>();
}
public void Save(uint seed)
{
var encrypt = Encryption.Encrypt(Data, seed, Header);
var encrypt = Encryption.Encrypt(RawData, seed, RawHeader);
File.WriteAllBytes(DataPath, encrypt.Data);
File.WriteAllBytes(HeaderPath, encrypt.Header);
}
@ -66,7 +70,7 @@ public void Hash()
if (details == null)
throw new ArgumentNullException(nameof(NameData));
foreach (var h in details.HashRegions)
Murmur3.UpdateMurmur32(Data, h.HashOffset, h.BeginOffset, (uint)h.Size);
Murmur3.UpdateMurmur32(Data.Slice(h.BeginOffset, h.Size), Data[h.HashOffset..]);
}
public IEnumerable<FileHashRegion> InvalidHashes()
@ -78,8 +82,8 @@ public IEnumerable<FileHashRegion> InvalidHashes()
throw new ArgumentNullException(nameof(NameData));
foreach (var h in details.HashRegions)
{
var current = Murmur3.GetMurmur3Hash(Data, h.BeginOffset, (uint)h.Size);
var saved = BitConverter.ToUInt32(Data, h.HashOffset);
var current = Murmur3.GetMurmur3Hash(Data.Slice(h.BeginOffset, h.Size));
var saved = ReadUInt32LittleEndian(Data[h.HashOffset..]);
if (current != saved)
yield return h;
}
@ -87,5 +91,4 @@ public IEnumerable<FileHashRegion> InvalidHashes()
protected string GetString(int offset, int maxLength) => StringUtil.GetString(Data, offset, maxLength);
protected static byte[] GetBytes(string value, int maxLength) => StringUtil.GetBytes(value, maxLength);
}
}

View File

@ -1,14 +1,14 @@
using System.Runtime.InteropServices;
// ReSharper disable NonReadonlyMemberInGetHashCode
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Metadata stored in a file's Header, indicating the revision information.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public sealed record FileHeaderInfo
{
/// <summary>
/// Metadata stored in a file's Header, indicating the revision information.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public sealed record FileHeaderInfo
{
public const int SIZE = 0x40;
[field: FieldOffset(0x00)] public uint Major { get; init; }
@ -17,5 +17,4 @@ public sealed record FileHeaderInfo
[field: FieldOffset(0x0A)] public ushort HeaderRevision { get; init; }
[field: FieldOffset(0x0C)] public ushort Unk2 { get; init; }
[field: FieldOffset(0x0E)] public ushort SaveRevision { get; init; }
}
}

View File

@ -1,13 +1,14 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Represents all saved data that is stored on the device for the New Horizon's game.
/// </summary>
public class HorizonSave
{
/// <summary>
/// Represents all saved data that is stored on the device for the New Horizon's game.
/// </summary>
public class HorizonSave
{
public readonly MainSave Main;
public readonly Player[] Players;
public override string ToString() => $"{Players[0].Personal.TownName} - {Players[0]}";
@ -51,7 +52,7 @@ public IEnumerable<FileHashRegion> GetInvalidHashes()
yield return hash;
}
public void ChangeIdentity(byte[] original, byte[] updated)
public void ChangeIdentity(ReadOnlySpan<byte> original, ReadOnlySpan<byte> updated)
{
Main.Data.ReplaceOccurrences(original, updated);
foreach (var pair in Players.SelectMany(z => z))
@ -98,5 +99,4 @@ public string GetBackupFolderTitle()
var timestamp = Main.LastSaved.TimeStamp.Replace(':', '.');
return StringUtil.CleanFileName($"{townName} - {timestamp}");
}
}
}

View File

@ -3,13 +3,13 @@
using System.IO;
using System.Linq;
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Stores references for all files in the Villager (<see cref="DirectoryName"/>) folder.
/// </summary>
public sealed class Player : IEnumerable<EncryptedFilePair>
{
/// <summary>
/// Stores references for all files in the Villager (<see cref="DirectoryName"/>) folder.
/// </summary>
public sealed class Player : IEnumerable<EncryptedFilePair>
{
public readonly Personal Personal;
public readonly PhotoStudioIsland Photo;
public readonly PostBox PostBox;
@ -24,9 +24,9 @@ public sealed class Player : IEnumerable<EncryptedFilePair>
#region Override Implementations
public IEnumerator<EncryptedFilePair> GetEnumerator()
{
IEnumerable<EncryptedFilePair> baseFiles = new EncryptedFilePair[] { Personal, Photo, PostBox, Profile };
IEnumerable<EncryptedFilePair> baseFiles = [Personal, Photo, PostBox, Profile];
if (WhereAreN is not null)
baseFiles = baseFiles.Concat(new EncryptedFilePair[] { WhereAreN });
baseFiles = baseFiles.Concat([WhereAreN]);
return baseFiles.AsEnumerable().GetEnumerator();
}
@ -61,5 +61,4 @@ private Player(string folder)
if (EncryptedFilePair.Exists(folder, WhereAreN.FileName))
WhereAreN = new WhereAreN(folder);
}
}
}

View File

@ -2,18 +2,18 @@
using System.Collections.Generic;
using static NHSE.Core.FileHashRevision;
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Logic for detecting a <see cref="EncryptedFilePair"/> revision.
/// </summary>
public static class RevisionChecker
{
/// <summary>
/// Logic for detecting a <see cref="EncryptedFilePair"/> revision.
/// </summary>
public static class RevisionChecker
{
/// <summary>
/// Unique save file size list by patch.
/// </summary>
private static readonly SaveFileSizes[] SizesByRevision =
{
[
new(REV_100_MAIN, REV_100_PERSONAL, REV_100_PHOTO, REV_100_POSTBOX, REV_100_PROFILE), // 1.0.0
new(REV_110_MAIN, REV_110_PERSONAL, REV_110_PHOTO, REV_110_POSTBOX, REV_110_PROFILE), // 1.1.0
new(REV_120_MAIN, REV_120_PERSONAL, REV_120_PHOTO, REV_120_POSTBOX, REV_120_PROFILE), // 1.2.0
@ -26,11 +26,11 @@ public static class RevisionChecker
new(REV_190_MAIN, REV_190_PERSONAL, REV_190_PHOTO, REV_190_POSTBOX, REV_190_PROFILE), // 1.9.0
new(REV_1100_MAIN,REV_1100_PERSONAL,REV_1100_PHOTO,REV_1100_POSTBOX,REV_1100_PROFILE),// 1.10.0
new(REV_1110_MAIN,REV_1110_PERSONAL,REV_1110_PHOTO,REV_1110_POSTBOX,REV_1110_PROFILE),// 1.11.0
new(REV_200_MAIN, REV_200_PERSONAL, REV_200_PHOTO, REV_200_POSTBOX, REV_200_PROFILE, REV_200_WHEREAREN), // 2.0.0
};
new(REV_200_MAIN, REV_200_PERSONAL, REV_200_PHOTO, REV_200_POSTBOX, REV_200_PROFILE, REV_200_WHEREAREN) // 2.0.0
];
private static readonly FileHeaderInfo[] RevisionInfo =
{
[
new() { Major = 0x00067, Minor = 0x0006F, HeaderRevision = 0, Unk1 = 2, SaveRevision = 00, Unk2 = 2 }, // 1.0.0
new() { Major = 0x0006D, Minor = 0x00078, HeaderRevision = 0, Unk1 = 2, SaveRevision = 01, Unk2 = 2 }, // 1.1.0
new() { Major = 0x0006D, Minor = 0x00078, HeaderRevision = 0, Unk1 = 2, SaveRevision = 02, Unk2 = 2 }, // 1.1.1
@ -61,11 +61,11 @@ public static class RevisionChecker
new() { Major = 0x80009, Minor = 0x80085, HeaderRevision = 0, Unk1 = 2, SaveRevision = 27, Unk2 = 2 }, // 2.0.5
new() { Major = 0x80009, Minor = 0x80085, HeaderRevision = 0, Unk1 = 2, SaveRevision = 28, Unk2 = 2 }, // 2.0.6
new() { Major = 0x80009, Minor = 0x80085, HeaderRevision = 0, Unk1 = 2, SaveRevision = 29, Unk2 = 2 }, // 2.0.7
new() { Major = 0x80009, Minor = 0x80085, HeaderRevision = 0, Unk1 = 2, SaveRevision = 30, Unk2 = 2 }, // 2.0.8
};
new() { Major = 0x80009, Minor = 0x80085, HeaderRevision = 0, Unk1 = 2, SaveRevision = 30, Unk2 = 2 } // 2.0.8
];
public static readonly IReadOnlyList<SaveFileSizes> SizeInfo = new[]
{
public static readonly IReadOnlyList<SaveFileSizes> SizeInfo =
[
SizesByRevision[0], // 1.0.0
SizesByRevision[1], // 1.1.0
SizesByRevision[1], // 1.1.1
@ -96,11 +96,11 @@ public static class RevisionChecker
SizesByRevision[12], // 2.0.5
SizesByRevision[12], // 2.0.6
SizesByRevision[12], // 2.0.7
SizesByRevision[12], // 2.0.8
};
SizesByRevision[12] // 2.0.8
];
public static readonly IReadOnlyList<FileHashInfo> HashInfo = new[]
{
public static readonly IReadOnlyList<FileHashInfo> HashInfo =
[
REV_100, // 1.0.0
REV_110, // 1.1.0
REV_110, // 1.1.1
@ -131,10 +131,12 @@ public static class RevisionChecker
REV_200, // 2.0.5
REV_200, // 2.0.6
REV_200, // 2.0.7
REV_200, // 2.0.8
};
REV_200 // 2.0.8
];
public static bool IsRevisionKnown(this FileHeaderInfo info) => info.GetKnownRevisionIndex() >= 0;
public static int GetKnownRevisionIndex(this FileHeaderInfo info) => Array.FindIndex(RevisionInfo, z => z.Equals(info));
extension(FileHeaderInfo info)
{
public bool IsRevisionKnown() => info.GetKnownRevisionIndex() >= 0;
public int GetKnownRevisionIndex() => Array.FindIndex(RevisionInfo, z => z.Equals(info));
}
}

View File

@ -1,10 +1,10 @@
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Stores file sizes for various savedata files at different patch revisions.
/// </summary>
public class SaveFileSizes
{
/// <summary>
/// Stores file sizes for various savedata files at different patch revisions.
/// </summary>
public class SaveFileSizes
{
public readonly uint Main;
public readonly uint Personal;
public readonly uint PhotoStudioIsland;
@ -21,5 +21,4 @@ public SaveFileSizes(uint main, uint personal, uint photo, uint postbox, uint pr
Profile = profile;
WhereAreN = wherearen;
}
}
}

View File

@ -1,12 +1,12 @@
using System;
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Offset info and object retrieval logic for <see cref="MainSave"/>
/// </summary>
public abstract class MainSaveOffsets
{
/// <summary>
/// Offset info and object retrieval logic for <see cref="MainSave"/>
/// </summary>
public abstract class MainSaveOffsets
{
public const int PlayerCount = 8;
public const int VillagerCount = 10;
public virtual int PatternCount => PatternCount1;
@ -54,10 +54,10 @@ public abstract class MainSaveOffsets
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 abstract IVillager ReadVillager(Memory<byte> data);
public abstract IVillagerHouse ReadVillagerHouse(Memory<byte> data);
public abstract IPlayerHouse ReadPlayerHouse(Memory<byte> data);
public abstract IPlayerRoom ReadPlayerRoom(Memory<byte> data);
public static MainSaveOffsets GetOffsets(FileHeaderInfo Info)
{
@ -99,33 +99,33 @@ public static MainSaveOffsets GetOffsets(FileHeaderInfo Info)
};
}
public DesignPattern ReadPattern(byte[] data, int index)
public DesignPattern ReadPattern(ReadOnlySpan<byte> data, int index)
{
if ((uint)index >= PatternCount)
throw new ArgumentOutOfRangeException(nameof(index));
return ReadPatternAtOffset(data, LandMyDesign + (index * DesignPattern.SIZE));
}
public static DesignPattern ReadPatternAtOffset(byte[] data, int offset)
public static DesignPattern ReadPatternAtOffset(ReadOnlySpan<byte> data, int offset)
{
var v = data.Slice(offset, DesignPattern.SIZE);
var v = data.Slice(offset, DesignPattern.SIZE).ToArray();
return new DesignPattern(v);
}
public void WritePattern(DesignPattern p, byte[] data, int index, byte[] playerID, byte[] townID)
public void WritePattern(DesignPattern p, Span<byte> data, int index, Span<byte> playerID, Span<byte> townID)
{
if ((uint)index >= PatternCount)
throw new ArgumentOutOfRangeException(nameof(index));
playerID.CopyTo(p.Data, 0x54); // overwrite playerID bytes so player owns
townID.CopyTo(p.Data, 0x38); // overwrite townID bytes so player owns
byte[] wipeflag = new byte[] { 0x02, 0xEE, 0x00, 0x00 }; // wipe so player owns
wipeflag.CopyTo(p.Data, 0x70);
p.Data.CopyTo(data, LandMyDesign + (index * DesignPattern.SIZE));
byte[] editedflag = new byte[] { 0x00 };
editedflag.CopyTo(data, PatternsEditFlagStart + index); // set edited flag for name import to work
playerID.CopyTo(p.Data[0x54..]); // overwrite playerID bytes so player owns
townID.CopyTo(p.Data[0x38..]); // overwrite townID bytes so player owns
ReadOnlySpan<byte> wipeflag = [0x02, 0xEE, 0x00, 0x00]; // wipe so player owns
wipeflag.CopyTo(p.Data[0x70..]);
p.Data.CopyTo(data[(LandMyDesign + (index * DesignPattern.SIZE))..]);
ReadOnlySpan<byte> editedflag = [0x00];
editedflag.CopyTo(data[(PatternsEditFlagStart + index)..]); // set edited flag for name import to work
}
public DesignPatternPRO ReadPatternPRO(byte[] data, int index)
public DesignPatternPRO ReadPatternPRO(ReadOnlySpan<byte> data, int index)
{
if ((uint)index >= PatternCount)
throw new ArgumentOutOfRangeException(nameof(index));
@ -133,77 +133,76 @@ public DesignPatternPRO ReadPatternPRO(byte[] data, int index)
return ReadPatternPROAtOffset(data, ofs);
}
public static DesignPatternPRO ReadPatternPROAtOffset(byte[] data, int ofs)
public static DesignPatternPRO ReadPatternPROAtOffset(ReadOnlySpan<byte> data, int ofs)
{
var v = data.Slice(ofs, DesignPatternPRO.SIZE);
var v = data.Slice(ofs, DesignPatternPRO.SIZE).ToArray();
return new DesignPatternPRO(v);
}
public void WritePatternPRO(DesignPatternPRO p, byte[] data, int index, byte[] playerID, byte[] townID)
public void WritePatternPRO(DesignPatternPRO p, Span<byte> data, int index, Span<byte> playerID, Span<byte> townID)
{
if ((uint)index >= PatternCount)
throw new ArgumentOutOfRangeException(nameof(index));
playerID.CopyTo(p.Data, 0x54); // overwrite playerID bytes so player owns
townID.CopyTo(p.Data, 0x38); // overwrite townID bytes so player owns
byte[] wipeflag = new byte[] { 0x00, 0x00, 0x00, 0x00 }; // wipe so player owns
wipeflag.CopyTo(p.Data, 0x70);
p.Data.CopyTo(data, PatternsPRO + (index * DesignPatternPRO.SIZE));
byte[] editedflag = new byte[] { 0x00 };
editedflag.CopyTo(data, PatternsProEditFlagStart + index);
playerID.CopyTo(p.Data[0x54..]); // overwrite playerID bytes so player owns
townID.CopyTo(p.Data[0x38..]); // overwrite townID bytes so player owns
ReadOnlySpan<byte> wipeflag = [0x00, 0x00, 0x00, 0x00]; // wipe so player owns
wipeflag.CopyTo(p.Data[0x70..]);
p.Data.CopyTo(data[(PatternsPRO + (index * DesignPatternPRO.SIZE))..]);
ReadOnlySpan<byte> editedflag = [0x00];
editedflag.CopyTo(data[(PatternsProEditFlagStart + index)..]); // set edited flag for name import to work
}
public IVillager ReadVillager(byte[] data, int index)
public IVillager ReadVillager(ReadOnlySpan<byte> data, int index)
{
if ((uint)index >= VillagerCount)
throw new ArgumentOutOfRangeException(nameof(index));
var size = VillagerSize;
var v = data.Slice(Animal + (index * size), size);
var v = data.Slice(Animal + (index * size), size).ToArray();
return ReadVillager(v);
}
public void WriteVillager(IVillager v, byte[] data, int index)
public void WriteVillager(IVillager v, Span<byte> data, int index)
{
if ((uint)index >= VillagerCount)
throw new ArgumentOutOfRangeException(nameof(index));
var size = VillagerSize;
v.Write().CopyTo(data, Animal + (index * size));
v.Write().CopyTo(data[(Animal + (index * size))..]);
}
public IVillagerHouse ReadVillagerHouse(byte[] data, int index)
public IVillagerHouse ReadVillagerHouse(ReadOnlySpan<byte> data, int index)
{
if ((uint)index >= VillagerCount)
throw new ArgumentOutOfRangeException(nameof(index));
var size = VillagerHouseSize;
var v = data.Slice(NpcHouseList + (index * size), size);
var v = data.Slice(NpcHouseList + (index * size), size).ToArray();
return ReadVillagerHouse(v);
}
public void WriteVillagerHouse(IVillagerHouse v, byte[] data, int index)
public void WriteVillagerHouse(IVillagerHouse v, Span<byte> data, int index)
{
if ((uint)index >= VillagerCount)
throw new ArgumentOutOfRangeException(nameof(index));
var size = VillagerHouseSize;
v.Write().CopyTo(data, NpcHouseList + (index * size));
v.Write().CopyTo(data[(NpcHouseList + (index * size))..]);
}
public IPlayerHouse ReadPlayerHouse(byte[] data, int index)
public IPlayerHouse ReadPlayerHouse(ReadOnlySpan<byte> data, int index)
{
if ((uint)index >= PlayerCount)
throw new ArgumentOutOfRangeException(nameof(index));
var size = PlayerHouseSize;
var v = data.Slice(PlayerHouseList + (index * size), size);
var v = data.Slice(PlayerHouseList + (index * size), size).ToArray();
return ReadPlayerHouse(v);
}
public void WritePlayerHouse(IPlayerHouse v, byte[] data, int index)
public void WritePlayerHouse(IPlayerHouse v, Span<byte> data, int index)
{
if ((uint)index >= PlayerCount)
throw new ArgumentOutOfRangeException(nameof(index));
var size = PlayerHouseSize;
v.Write().CopyTo(data, PlayerHouseList + (index * size));
}
v.Write().CopyTo(data[(PlayerHouseList + (index * size))..]);
}
}

View File

@ -1,10 +1,12 @@
namespace NHSE.Core
using System;
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets10 : MainSaveOffsets
{
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets10 : MainSaveOffsets
{
#region GSaveLand
public const int GSaveLandStart = 0x108;
public override int Animal => GSaveLandStart + 0x08;
@ -51,15 +53,14 @@ public class MainSaveOffsets10 : MainSaveOffsets
#endregion
public override int VillagerSize => Villager1.SIZE;
public override IVillager ReadVillager(byte[] data) => new Villager1(data);
public override IVillager ReadVillager(Memory<byte> data) => new Villager1(data);
public override int VillagerHouseSize => VillagerHouse1.SIZE;
public override IVillagerHouse ReadVillagerHouse(byte[] data) => new VillagerHouse1(data);
public override IVillagerHouse ReadVillagerHouse(Memory<byte> data) => new VillagerHouse1(data);
public override int PlayerHouseSize => PlayerHouse1.SIZE;
public override IPlayerHouse ReadPlayerHouse(byte[] data) => new PlayerHouse1(data);
public override IPlayerHouse ReadPlayerHouse(Memory<byte> data) => new PlayerHouse1(data);
public override int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data);
}
public override IPlayerRoom ReadPlayerRoom(Memory<byte> data) => new PlayerRoom1(data);
}

View File

@ -1,10 +1,12 @@
namespace NHSE.Core
using System;
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets11 : MainSaveOffsets
{
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets11 : MainSaveOffsets
{
#region GSaveLand
public const int GSaveLandStart = 0x110;
public override int Animal => GSaveLandStart + 0x10;
@ -51,14 +53,13 @@ public class MainSaveOffsets11 : MainSaveOffsets
#endregion
public override int VillagerSize => Villager1.SIZE;
public override IVillager ReadVillager(byte[] data) => new Villager1(data);
public override IVillager ReadVillager(Memory<byte> data) => new Villager1(data);
public override int VillagerHouseSize => VillagerHouse1.SIZE;
public override IVillagerHouse ReadVillagerHouse(byte[] data) => new VillagerHouse1(data);
public override IVillagerHouse ReadVillagerHouse(Memory<byte> data) => new VillagerHouse1(data);
public override int PlayerHouseSize => PlayerHouse1.SIZE;
public override IPlayerHouse ReadPlayerHouse(byte[] data) => new PlayerHouse1(data);
public override IPlayerHouse ReadPlayerHouse(Memory<byte> data) => new PlayerHouse1(data);
public override int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data);
}
public override IPlayerRoom ReadPlayerRoom(Memory<byte> data) => new PlayerRoom1(data);
}

View File

@ -1,10 +1,12 @@
namespace NHSE.Core
using System;
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets110 : MainSaveOffsets
{
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets110 : MainSaveOffsets
{
public override int PatternCount => PatternCount2;
#region GSaveLand
@ -53,14 +55,13 @@ public class MainSaveOffsets110 : MainSaveOffsets
#endregion
public override int VillagerSize => Villager2.SIZE;
public override IVillager ReadVillager(byte[] data) => new Villager2(data);
public override IVillager ReadVillager(Memory<byte> data) => new Villager2(data);
public override int VillagerHouseSize => VillagerHouse1.SIZE;
public override IVillagerHouse ReadVillagerHouse(byte[] data) => new VillagerHouse1(data);
public override IVillagerHouse ReadVillagerHouse(Memory<byte> data) => new VillagerHouse1(data);
public override int PlayerHouseSize => PlayerHouse1.SIZE;
public override IPlayerHouse ReadPlayerHouse(byte[] data) => new PlayerHouse1(data);
public override IPlayerHouse ReadPlayerHouse(Memory<byte> data) => new PlayerHouse1(data);
public override int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data);
}
public override IPlayerRoom ReadPlayerRoom(Memory<byte> data) => new PlayerRoom1(data);
}

View File

@ -1,11 +1,13 @@
namespace NHSE.Core
using System;
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
/// <remarks>Same as <see cref="MainSaveOffsets110"/></remarks>
public class MainSaveOffsets111 : MainSaveOffsets
{
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
/// <remarks>Same as <see cref="MainSaveOffsets110"/></remarks>
public class MainSaveOffsets111 : MainSaveOffsets
{
public override int PatternCount => PatternCount2;
#region GSaveLand
@ -54,14 +56,13 @@ public class MainSaveOffsets111 : MainSaveOffsets
#endregion
public override int VillagerSize => Villager2.SIZE;
public override IVillager ReadVillager(byte[] data) => new Villager2(data);
public override IVillager ReadVillager(Memory<byte> data) => new Villager2(data);
public override int VillagerHouseSize => VillagerHouse1.SIZE;
public override IVillagerHouse ReadVillagerHouse(byte[] data) => new VillagerHouse1(data);
public override IVillagerHouse ReadVillagerHouse(Memory<byte> data) => new VillagerHouse1(data);
public override int PlayerHouseSize => PlayerHouse1.SIZE;
public override IPlayerHouse ReadPlayerHouse(byte[] data) => new PlayerHouse1(data);
public override IPlayerHouse ReadPlayerHouse(Memory<byte> data) => new PlayerHouse1(data);
public override int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data);
}
public override IPlayerRoom ReadPlayerRoom(Memory<byte> data) => new PlayerRoom1(data);
}

View File

@ -1,10 +1,12 @@
namespace NHSE.Core
using System;
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets12 : MainSaveOffsets
{
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets12 : MainSaveOffsets
{
#region GSaveLand
public const int GSaveLandStart = 0x110;
public override int Animal => GSaveLandStart + 0x10;
@ -51,14 +53,13 @@ public class MainSaveOffsets12 : MainSaveOffsets
#endregion
public override int VillagerSize => Villager1.SIZE;
public override IVillager ReadVillager(byte[] data) => new Villager1(data);
public override IVillager ReadVillager(Memory<byte> data) => new Villager1(data);
public override int VillagerHouseSize => VillagerHouse1.SIZE;
public override IVillagerHouse ReadVillagerHouse(byte[] data) => new VillagerHouse1(data);
public override IVillagerHouse ReadVillagerHouse(Memory<byte> data) => new VillagerHouse1(data);
public override int PlayerHouseSize => PlayerHouse1.SIZE;
public override IPlayerHouse ReadPlayerHouse(byte[] data) => new PlayerHouse1(data);
public override IPlayerHouse ReadPlayerHouse(Memory<byte> data) => new PlayerHouse1(data);
public override int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data);
}
public override IPlayerRoom ReadPlayerRoom(Memory<byte> data) => new PlayerRoom1(data);
}

View File

@ -1,10 +1,12 @@
namespace NHSE.Core
using System;
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets13 : MainSaveOffsets
{
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets13 : MainSaveOffsets
{
#region GSaveLand
public const int GSaveLandStart = 0x110;
public override int Animal => GSaveLandStart + 0x10;
@ -51,14 +53,13 @@ public class MainSaveOffsets13 : MainSaveOffsets
#endregion
public override int VillagerSize => Villager1.SIZE;
public override IVillager ReadVillager(byte[] data) => new Villager1(data);
public override IVillager ReadVillager(Memory<byte> data) => new Villager1(data);
public override int VillagerHouseSize => VillagerHouse1.SIZE;
public override IVillagerHouse ReadVillagerHouse(byte[] data) => new VillagerHouse1(data);
public override IVillagerHouse ReadVillagerHouse(Memory<byte> data) => new VillagerHouse1(data);
public override int PlayerHouseSize => PlayerHouse1.SIZE;
public override IPlayerHouse ReadPlayerHouse(byte[] data) => new PlayerHouse1(data);
public override IPlayerHouse ReadPlayerHouse(Memory<byte> data) => new PlayerHouse1(data);
public override int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data);
}
public override IPlayerRoom ReadPlayerRoom(Memory<byte> data) => new PlayerRoom1(data);
}

View File

@ -1,10 +1,12 @@
namespace NHSE.Core
using System;
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets14 : MainSaveOffsets
{
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets14 : MainSaveOffsets
{
#region GSaveLand
public const int GSaveLandStart = 0x110;
public override int Animal => GSaveLandStart + 0x10;
@ -51,14 +53,13 @@ public class MainSaveOffsets14 : MainSaveOffsets
#endregion
public override int VillagerSize => Villager1.SIZE;
public override IVillager ReadVillager(byte[] data) => new Villager1(data);
public override IVillager ReadVillager(Memory<byte> data) => new Villager1(data);
public override int VillagerHouseSize => VillagerHouse1.SIZE;
public override IVillagerHouse ReadVillagerHouse(byte[] data) => new VillagerHouse1(data);
public override IVillagerHouse ReadVillagerHouse(Memory<byte> data) => new VillagerHouse1(data);
public override int PlayerHouseSize => PlayerHouse1.SIZE;
public override IPlayerHouse ReadPlayerHouse(byte[] data) => new PlayerHouse1(data);
public override IPlayerHouse ReadPlayerHouse(Memory<byte> data) => new PlayerHouse1(data);
public override int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data);
}
public override IPlayerRoom ReadPlayerRoom(Memory<byte> data) => new PlayerRoom1(data);
}

View File

@ -1,10 +1,12 @@
namespace NHSE.Core
using System;
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets15 : MainSaveOffsets
{
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets15 : MainSaveOffsets
{
#region GSaveLand
public const int GSaveLandStart = 0x110;
public override int Animal => GSaveLandStart + 0x10;
@ -51,14 +53,13 @@ public class MainSaveOffsets15 : MainSaveOffsets
#endregion
public override int VillagerSize => Villager2.SIZE;
public override IVillager ReadVillager(byte[] data) => new Villager2(data);
public override IVillager ReadVillager(Memory<byte> data) => new Villager2(data);
public override int VillagerHouseSize => VillagerHouse1.SIZE;
public override IVillagerHouse ReadVillagerHouse(byte[] data) => new VillagerHouse1(data);
public override IVillagerHouse ReadVillagerHouse(Memory<byte> data) => new VillagerHouse1(data);
public override int PlayerHouseSize => PlayerHouse1.SIZE;
public override IPlayerHouse ReadPlayerHouse(byte[] data) => new PlayerHouse1(data);
public override IPlayerHouse ReadPlayerHouse(Memory<byte> data) => new PlayerHouse1(data);
public override int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data);
}
public override IPlayerRoom ReadPlayerRoom(Memory<byte> data) => new PlayerRoom1(data);
}

View File

@ -1,10 +1,12 @@
namespace NHSE.Core
using System;
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets16 : MainSaveOffsets
{
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets16 : MainSaveOffsets
{
#region GSaveLand
public const int GSaveLandStart = 0x110;
public override int Animal => GSaveLandStart + 0x10;
@ -51,14 +53,13 @@ public class MainSaveOffsets16 : MainSaveOffsets
#endregion
public override int VillagerSize => Villager2.SIZE;
public override IVillager ReadVillager(byte[] data) => new Villager2(data);
public override IVillager ReadVillager(Memory<byte> data) => new Villager2(data);
public override int VillagerHouseSize => VillagerHouse1.SIZE;
public override IVillagerHouse ReadVillagerHouse(byte[] data) => new VillagerHouse1(data);
public override IVillagerHouse ReadVillagerHouse(Memory<byte> data) => new VillagerHouse1(data);
public override int PlayerHouseSize => PlayerHouse1.SIZE;
public override IPlayerHouse ReadPlayerHouse(byte[] data) => new PlayerHouse1(data);
public override IPlayerHouse ReadPlayerHouse(Memory<byte> data) => new PlayerHouse1(data);
public override int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data);
}
public override IPlayerRoom ReadPlayerRoom(Memory<byte> data) => new PlayerRoom1(data);
}

View File

@ -1,10 +1,12 @@
namespace NHSE.Core
using System;
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets17 : MainSaveOffsets
{
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets17 : MainSaveOffsets
{
#region GSaveLand
public const int GSaveLandStart = 0x110;
public override int Animal => GSaveLandStart + 0x10;
@ -51,14 +53,13 @@ public class MainSaveOffsets17 : MainSaveOffsets
#endregion
public override int VillagerSize => Villager2.SIZE;
public override IVillager ReadVillager(byte[] data) => new Villager2(data);
public override IVillager ReadVillager(Memory<byte> data) => new Villager2(data);
public override int VillagerHouseSize => VillagerHouse1.SIZE;
public override IVillagerHouse ReadVillagerHouse(byte[] data) => new VillagerHouse1(data);
public override IVillagerHouse ReadVillagerHouse(Memory<byte> data) => new VillagerHouse1(data);
public override int PlayerHouseSize => PlayerHouse1.SIZE;
public override IPlayerHouse ReadPlayerHouse(byte[] data) => new PlayerHouse1(data);
public override IPlayerHouse ReadPlayerHouse(Memory<byte> data) => new PlayerHouse1(data);
public override int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data);
}
public override IPlayerRoom ReadPlayerRoom(Memory<byte> data) => new PlayerRoom1(data);
}

View File

@ -1,11 +1,13 @@
namespace NHSE.Core
using System;
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
/// <remarks>Same as <see cref="MainSaveOffsets17"/></remarks>.
public class MainSaveOffsets18 : MainSaveOffsets
{
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
/// <remarks>Same as <see cref="MainSaveOffsets17"/></remarks>.
public class MainSaveOffsets18 : MainSaveOffsets
{
#region GSaveLand
public const int GSaveLandStart = 0x110;
public override int Animal => GSaveLandStart + 0x10;
@ -52,14 +54,13 @@ public class MainSaveOffsets18 : MainSaveOffsets
#endregion
public override int VillagerSize => Villager2.SIZE;
public override IVillager ReadVillager(byte[] data) => new Villager2(data);
public override IVillager ReadVillager(Memory<byte> data) => new Villager2(data);
public override int VillagerHouseSize => VillagerHouse1.SIZE;
public override IVillagerHouse ReadVillagerHouse(byte[] data) => new VillagerHouse1(data);
public override IVillagerHouse ReadVillagerHouse(Memory<byte> data) => new VillagerHouse1(data);
public override int PlayerHouseSize => PlayerHouse1.SIZE;
public override IPlayerHouse ReadPlayerHouse(byte[] data) => new PlayerHouse1(data);
public override IPlayerHouse ReadPlayerHouse(Memory<byte> data) => new PlayerHouse1(data);
public override int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data);
}
public override IPlayerRoom ReadPlayerRoom(Memory<byte> data) => new PlayerRoom1(data);
}

View File

@ -1,10 +1,12 @@
namespace NHSE.Core
using System;
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets19 : MainSaveOffsets
{
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
public class MainSaveOffsets19 : MainSaveOffsets
{
public override int PatternCount => PatternCount2;
#region GSaveLand
@ -53,14 +55,13 @@ public class MainSaveOffsets19 : MainSaveOffsets
#endregion
public override int VillagerSize => Villager2.SIZE;
public override IVillager ReadVillager(byte[] data) => new Villager2(data);
public override IVillager ReadVillager(Memory<byte> data) => new Villager2(data);
public override int VillagerHouseSize => VillagerHouse1.SIZE;
public override IVillagerHouse ReadVillagerHouse(byte[] data) => new VillagerHouse1(data);
public override IVillagerHouse ReadVillagerHouse(Memory<byte> data) => new VillagerHouse1(data);
public override int PlayerHouseSize => PlayerHouse1.SIZE;
public override IPlayerHouse ReadPlayerHouse(byte[] data) => new PlayerHouse1(data);
public override IPlayerHouse ReadPlayerHouse(Memory<byte> data) => new PlayerHouse1(data);
public override int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data);
}
public override IPlayerRoom ReadPlayerRoom(Memory<byte> data) => new PlayerRoom1(data);
}

View File

@ -1,11 +1,13 @@
namespace NHSE.Core
using System;
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
/// <remarks>Same as <see cref="MainSaveOffsets110"/></remarks>
public class MainSaveOffsets20 : MainSaveOffsets
{
/// <summary>
/// <inheritdoc cref="MainSaveOffsets"/>
/// </summary>
/// <remarks>Same as <see cref="MainSaveOffsets110"/></remarks>
public class MainSaveOffsets20 : MainSaveOffsets
{
public override int PatternCount => PatternCount2;
#region GSaveLand
@ -54,14 +56,13 @@ public class MainSaveOffsets20 : MainSaveOffsets
#endregion
public override int VillagerSize => Villager2.SIZE;
public override IVillager ReadVillager(byte[] data) => new Villager2(data);
public override IVillager ReadVillager(Memory<byte> data) => new Villager2(data);
public override int VillagerHouseSize => VillagerHouse2.SIZE;
public override IVillagerHouse ReadVillagerHouse(byte[] data) => new VillagerHouse2(data);
public override IVillagerHouse ReadVillagerHouse(Memory<byte> data) => new VillagerHouse2(data);
public override int PlayerHouseSize => PlayerHouse2.SIZE;
public override IPlayerHouse ReadPlayerHouse(byte[] data) => new PlayerHouse2(data);
public override IPlayerHouse ReadPlayerHouse(Memory<byte> data) => new PlayerHouse2(data);
public override int PlayerRoomSize => PlayerRoom2.SIZE;
public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom2(data);
}
public override IPlayerRoom ReadPlayerRoom(Memory<byte> data) => new PlayerRoom2(data);
}

View File

@ -1,12 +1,12 @@
using System;
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Offset info and object retrieval logic for <see cref="Personal"/>
/// </summary>
public abstract class PersonalOffsets
{
/// <summary>
/// Offset info and object retrieval logic for <see cref="Personal"/>
/// </summary>
public abstract class PersonalOffsets
{
public abstract int PersonalId { get; }
public abstract int EventFlagsPlayer { get; }
public abstract int CountAchievement { get; }
@ -79,7 +79,6 @@ public static PersonalOffsets GetOffsets(FileHeaderInfo Info)
};
}
public abstract IReactionStore ReadReactions(byte[] data);
public abstract void SetReactions(byte[] data, IReactionStore value);
}
public abstract IReactionStore ReadReactions(ReadOnlySpan<byte> data);
public abstract void SetReactions(Span<byte> data, IReactionStore value);
}

View File

@ -1,10 +1,12 @@
namespace NHSE.Core
using System;
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
public sealed class PersonalOffsets10 : PersonalOffsets
{
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
public sealed class PersonalOffsets10 : PersonalOffsets
{
private const int Player = 0x108;
public override int PersonalId => Player + 0xAF98;
@ -41,7 +43,6 @@ public sealed class PersonalOffsets10 : PersonalOffsets
public override int MaxRecipeID => 0x2A0;
public override int MaxRemakeBitFlag => 0x754 * 32;
public override IReactionStore ReadReactions(byte[] data) => data.Slice(Manpu, GSavePlayerManpu.SIZE).ToStructure<GSavePlayerManpu>();
public override void SetReactions(byte[] data, IReactionStore value) => ((GSavePlayerManpu)value).ToBytes().CopyTo(data, Manpu);
}
public override IReactionStore ReadReactions(ReadOnlySpan<byte> data) => data.Slice(Manpu, GSavePlayerManpu.SIZE).ToStructure<GSavePlayerManpu>();
public override void SetReactions(Span<byte> data, IReactionStore value) => ((GSavePlayerManpu)value).ToBytes().CopyTo(data[Manpu..]);
}

View File

@ -1,10 +1,12 @@
namespace NHSE.Core
using System;
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
public sealed class PersonalOffsets11 : PersonalOffsets
{
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
public sealed class PersonalOffsets11 : PersonalOffsets
{
private const int Player = 0x110;
public override int PersonalId => Player + 0xAFA8;
@ -41,7 +43,6 @@ public sealed class PersonalOffsets11 : PersonalOffsets
public override int MaxRecipeID => 0x2C8;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override IReactionStore ReadReactions(byte[] data) => data.Slice(Manpu, GSavePlayerManpu.SIZE).ToStructure<GSavePlayerManpu>();
public override void SetReactions(byte[] data, IReactionStore value) => ((GSavePlayerManpu)value).ToBytes().CopyTo(data, Manpu);
}
public override IReactionStore ReadReactions(ReadOnlySpan<byte> data) => data.Slice(Manpu, GSavePlayerManpu.SIZE).ToStructure<GSavePlayerManpu>();
public override void SetReactions(Span<byte> data, IReactionStore value) => ((GSavePlayerManpu)value).ToBytes().CopyTo(data[Manpu..]);
}

View File

@ -1,11 +1,13 @@
namespace NHSE.Core
using System;
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
/// <remarks>Same as <see cref="PersonalOffsets18"/>.</remarks>
public sealed class PersonalOffsets110 : PersonalOffsets
{
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
/// <remarks>Same as <see cref="PersonalOffsets18"/>.</remarks>
public sealed class PersonalOffsets110 : PersonalOffsets
{
private const int Player = 0x110;
public override int PersonalId => Player + 0xAFA8;
@ -42,7 +44,6 @@ public sealed class PersonalOffsets110 : PersonalOffsets
public override int MaxRecipeID => 0x308;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override IReactionStore ReadReactions(byte[] data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(byte[] data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data, Manpu);
}
public override IReactionStore ReadReactions(ReadOnlySpan<byte> data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(Span<byte> data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data[Manpu..]);
}

View File

@ -1,11 +1,13 @@
namespace NHSE.Core
using System;
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
/// <remarks>Same as <see cref="PersonalOffsets18"/>.</remarks>
public sealed class PersonalOffsets111 : PersonalOffsets
{
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
/// <remarks>Same as <see cref="PersonalOffsets18"/>.</remarks>
public sealed class PersonalOffsets111 : PersonalOffsets
{
private const int Player = 0x110;
public override int PersonalId => Player + 0xAFA8;
@ -42,7 +44,6 @@ public sealed class PersonalOffsets111 : PersonalOffsets
public override int MaxRecipeID => 0x3D6;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override IReactionStore ReadReactions(byte[] data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(byte[] data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data, Manpu);
}
public override IReactionStore ReadReactions(ReadOnlySpan<byte> data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(Span<byte> data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data[Manpu..]);
}

View File

@ -1,10 +1,12 @@
namespace NHSE.Core
using System;
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
public sealed class PersonalOffsets12 : PersonalOffsets
{
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
public sealed class PersonalOffsets12 : PersonalOffsets
{
private const int Player = 0x110;
public override int PersonalId => Player + 0xAFA8;
@ -41,7 +43,6 @@ public sealed class PersonalOffsets12 : PersonalOffsets
public override int MaxRecipeID => 0x2DA; // mermaid stuff
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override IReactionStore ReadReactions(byte[] data) => data.Slice(Manpu, GSavePlayerManpu.SIZE).ToStructure<GSavePlayerManpu>();
public override void SetReactions(byte[] data, IReactionStore value) => ((GSavePlayerManpu)value).ToBytes().CopyTo(data, Manpu);
}
public override IReactionStore ReadReactions(ReadOnlySpan<byte> data) => data.Slice(Manpu, GSavePlayerManpu.SIZE).ToStructure<GSavePlayerManpu>();
public override void SetReactions(Span<byte> data, IReactionStore value) => ((GSavePlayerManpu)value).ToBytes().CopyTo(data[Manpu..]);
}

View File

@ -1,10 +1,12 @@
namespace NHSE.Core
using System;
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
public sealed class PersonalOffsets13 : PersonalOffsets
{
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
public sealed class PersonalOffsets13 : PersonalOffsets
{
private const int Player = 0x110;
public override int PersonalId => Player + 0xAFA8;
@ -41,7 +43,6 @@ public sealed class PersonalOffsets13 : PersonalOffsets
public override int MaxRecipeID => 0x2DA;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override IReactionStore ReadReactions(byte[] data) => data.Slice(Manpu, GSavePlayerManpu.SIZE).ToStructure<GSavePlayerManpu>();
public override void SetReactions(byte[] data, IReactionStore value) => ((GSavePlayerManpu)value).ToBytes().CopyTo(data, Manpu);
}
public override IReactionStore ReadReactions(ReadOnlySpan<byte> data) => data.Slice(Manpu, GSavePlayerManpu.SIZE).ToStructure<GSavePlayerManpu>();
public override void SetReactions(Span<byte> data, IReactionStore value) => ((GSavePlayerManpu)value).ToBytes().CopyTo(data[Manpu..]);
}

View File

@ -1,10 +1,12 @@
namespace NHSE.Core
using System;
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
public sealed class PersonalOffsets14 : PersonalOffsets
{
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
public sealed class PersonalOffsets14 : PersonalOffsets
{
private const int Player = 0x110;
public override int PersonalId => Player + 0xAFA8;
@ -41,7 +43,6 @@ public sealed class PersonalOffsets14 : PersonalOffsets
public override int MaxRecipeID => 0x2DA;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override IReactionStore ReadReactions(byte[] data) => data.Slice(Manpu, GSavePlayerManpu.SIZE).ToStructure<GSavePlayerManpu>();
public override void SetReactions(byte[] data, IReactionStore value) => ((GSavePlayerManpu)value).ToBytes().CopyTo(data, Manpu);
}
public override IReactionStore ReadReactions(ReadOnlySpan<byte> data) => data.Slice(Manpu, GSavePlayerManpu.SIZE).ToStructure<GSavePlayerManpu>();
public override void SetReactions(Span<byte> data, IReactionStore value) => ((GSavePlayerManpu)value).ToBytes().CopyTo(data[Manpu..]);
}

View File

@ -1,10 +1,12 @@
namespace NHSE.Core
using System;
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
public sealed class PersonalOffsets15 : PersonalOffsets
{
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
public sealed class PersonalOffsets15 : PersonalOffsets
{
private const int Player = 0x110;
public override int PersonalId => Player + 0xAFA8;
@ -41,7 +43,6 @@ public sealed class PersonalOffsets15 : PersonalOffsets
public override int MaxRecipeID => 0x2E1;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override IReactionStore ReadReactions(byte[] data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(byte[] data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data, Manpu);
}
public override IReactionStore ReadReactions(ReadOnlySpan<byte> data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(Span<byte> data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data[Manpu..]);
}

View File

@ -1,10 +1,12 @@
namespace NHSE.Core
using System;
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
public sealed class PersonalOffsets16 : PersonalOffsets
{
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
public sealed class PersonalOffsets16 : PersonalOffsets
{
private const int Player = 0x110;
public override int PersonalId => Player + 0xAFA8;
@ -41,7 +43,6 @@ public sealed class PersonalOffsets16 : PersonalOffsets
public override int MaxRecipeID => 0x2E1;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override IReactionStore ReadReactions(byte[] data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(byte[] data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data, Manpu);
}
public override IReactionStore ReadReactions(ReadOnlySpan<byte> data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(Span<byte> data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data[Manpu..]);
}

View File

@ -1,10 +1,12 @@
namespace NHSE.Core
using System;
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
public sealed class PersonalOffsets17 : PersonalOffsets
{
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
public sealed class PersonalOffsets17 : PersonalOffsets
{
private const int Player = 0x110;
public override int PersonalId => Player + 0xAFA8;
@ -41,7 +43,6 @@ public sealed class PersonalOffsets17 : PersonalOffsets
public override int MaxRecipeID => 0x308;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override IReactionStore ReadReactions(byte[] data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(byte[] data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data, Manpu);
}
public override IReactionStore ReadReactions(ReadOnlySpan<byte> data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(Span<byte> data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data[Manpu..]);
}

View File

@ -1,11 +1,13 @@
namespace NHSE.Core
using System;
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
/// <remarks>Same as <see cref="PersonalOffsets17"/>.</remarks>
public sealed class PersonalOffsets18 : PersonalOffsets
{
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
/// <remarks>Same as <see cref="PersonalOffsets17"/>.</remarks>
public sealed class PersonalOffsets18 : PersonalOffsets
{
private const int Player = 0x110;
public override int PersonalId => Player + 0xAFA8;
@ -42,7 +44,6 @@ public sealed class PersonalOffsets18 : PersonalOffsets
public override int MaxRecipeID => 0x308;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override IReactionStore ReadReactions(byte[] data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(byte[] data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data, Manpu);
}
public override IReactionStore ReadReactions(ReadOnlySpan<byte> data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(Span<byte> data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data[Manpu..]);
}

View File

@ -1,11 +1,13 @@
namespace NHSE.Core
using System;
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
/// <remarks>Same as <see cref="PersonalOffsets18"/>.</remarks>
public sealed class PersonalOffsets19 : PersonalOffsets
{
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
/// <remarks>Same as <see cref="PersonalOffsets18"/>.</remarks>
public sealed class PersonalOffsets19 : PersonalOffsets
{
private const int Player = 0x110;
public override int PersonalId => Player + 0xAFA8;
@ -42,7 +44,6 @@ public sealed class PersonalOffsets19 : PersonalOffsets
public override int MaxRecipeID => 0x308;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override IReactionStore ReadReactions(byte[] data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(byte[] data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data, Manpu);
}
public override IReactionStore ReadReactions(ReadOnlySpan<byte> data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(Span<byte> data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data[Manpu..]);
}

View File

@ -1,10 +1,12 @@
namespace NHSE.Core
using System;
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
public sealed class PersonalOffsets20 : PersonalOffsets
{
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
public sealed class PersonalOffsets20 : PersonalOffsets
{
private const int Player = 0x110;
public override int PersonalId => Player + 0xAFA8;
@ -41,7 +43,6 @@ public sealed class PersonalOffsets20 : PersonalOffsets
public override int MaxRecipeID => 0x430;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override IReactionStore ReadReactions(byte[] data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(byte[] data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data, Manpu);
}
public override IReactionStore ReadReactions(ReadOnlySpan<byte> data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(Span<byte> data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data[Manpu..]);
}

View File

@ -1,12 +1,12 @@
using System;
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Offset info and object retrieval logic for <see cref="Personal"/>
/// </summary>
public abstract class WhereAreNOffsets
{
/// <summary>
/// Offset info and object retrieval logic for <see cref="Personal"/>
/// </summary>
public abstract class WhereAreNOffsets
{
public abstract int Poki { get; }
public static WhereAreNOffsets GetOffsets(FileHeaderInfo Info)
@ -26,5 +26,4 @@ public static WhereAreNOffsets GetOffsets(FileHeaderInfo Info)
_ => throw new IndexOutOfRangeException("Unknown revision!" + Environment.NewLine + Info),
};
}
}
}

View File

@ -1,10 +1,9 @@
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
public sealed class WhereAreNOffsets20 : WhereAreNOffsets
{
/// <summary>
/// <inheritdoc cref="PersonalOffsets"/>
/// </summary>
public sealed class WhereAreNOffsets20 : WhereAreNOffsets
{
public override int Poki => 0xB84228;
}
}

View File

@ -1,10 +1,10 @@
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Global repository for game strings; initialized to a specified language.
/// </summary>
public static class GameInfo
{
/// <summary>
/// Global repository for game strings; initialized to a specified language.
/// </summary>
public static class GameInfo
{
private static readonly GameStrings?[] Languages = new GameStrings[GameLanguage.LanguageCount];
public static string CurrentLanguage { get; private set; } = GameLanguage.DefaultLanguage;
@ -32,5 +32,4 @@ public static string SetLanguage2Char(int index)
Strings = GetStrings(lang);
return lang;
}
}
}

View File

@ -1,12 +1,12 @@
using System;
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Metadata for dealing with language codes
/// </summary>
public static class GameLanguage
{
/// <summary>
/// Metadata for dealing with language codes
/// </summary>
public static class GameLanguage
{
public const string DefaultLanguage = "en"; // English
public static int DefaultLanguageIndex => Array.IndexOf(LanguageCodes, DefaultLanguage);
public static string Language2Char(int lang) => lang > LanguageCodes.Length ? DefaultLanguage : LanguageCodes[lang];
@ -22,7 +22,7 @@ public static int GetLanguageIndex(string lang)
/// <summary>
/// Language codes supported for loading string resources
/// </summary>
private static readonly string[] LanguageCodes = { "en", "jp", "de", "es", "fr", "it", "ko", "zhs", "zht" };
private static readonly string[] LanguageCodes = ["en", "jp", "de", "es", "fr", "it", "ko", "zhs", "zht"];
public static string[] GetStrings(string ident, string lang, string type = "text")
{
@ -32,5 +32,4 @@ public static string[] GetStrings(string ident, string lang, string type = "text
return data;
}
}
}

View File

@ -1,12 +1,13 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Stores game localization strings for use by logic.
/// </summary>
public sealed class GameStrings : IRemakeString
{
/// <summary>
/// Stores game localization strings for use by logic.
/// </summary>
public sealed class GameStrings : IRemakeString
{
private readonly string lang;
public readonly string[] villagers;
@ -16,7 +17,7 @@ public sealed class GameStrings : IRemakeString
public readonly Dictionary<string, string> VillagerMap;
public readonly Dictionary<string, string> VillagerDefaultPhraseMap;
public readonly List<ComboItem> ItemDataSource;
public readonly Dictionary<string, string> InternalNameTranslation = new();
public readonly Dictionary<string, string> InternalNameTranslation = [];
public IReadOnlyDictionary<string, string> BodyParts { get; }
public IReadOnlyDictionary<string, string> BodyColor { get; }
@ -42,7 +43,7 @@ public GameStrings(string l)
FabricColor = GetDictionary(Get("fabric_color"));
}
private static IReadOnlyDictionary<string, string> GetDictionary(IEnumerable<string> lines, char split = '\t')
private static Dictionary<string, string> GetDictionary(ReadOnlySpan<string> lines, char split = '\t')
{
var result = new Dictionary<string, string>();
foreach (var s in lines)
@ -50,14 +51,14 @@ public GameStrings(string l)
if (s.Length == 0)
continue;
var index = s.IndexOf(split);
var key = s.Substring(0, index);
var value = s.Substring(index + 1);
var key = s[..index];
var value = s[(index + 1)..];
result.Add(key, value);
}
return result;
}
private List<ComboItem> CreateItemDataSource(string[] strings)
private List<ComboItem> CreateItemDataSource(ReadOnlySpan<string> strings)
{
var dataSource = ComboItemUtil.GetArray(strings);
@ -68,10 +69,10 @@ private List<ComboItem> CreateItemDataSource(string[] strings)
return dataSource;
}
public List<ComboItem> CreateItemDataSource(IReadOnlyCollection<ushort> dict, bool none = true)
public List<ComboItem> CreateItemDataSource(ReadOnlySpan<ushort> dict, bool none = true)
{
var display = itemlistdisplay;
var result = new List<ComboItem>(dict.Count);
var result = new List<ComboItem>(dict.Length);
foreach (var x in dict)
result.Add(new ComboItem(display[x], x));
@ -96,34 +97,28 @@ public List<ComboItem> CreateItemDataSource(IReadOnlyCollection<KeyValuePair<ush
return result;
}
private static Dictionary<string, string> GetMap(IReadOnlyCollection<string> arr)
private static Dictionary<string, string> GetMap(ReadOnlySpan<string> arr)
{
var map = new Dictionary<string, string>(arr.Count);
var map = new Dictionary<string, string>(arr.Length);
foreach (var kvp in arr)
{
var index = kvp.IndexOf('\t');
if (index < 0)
continue;
var abbrev = kvp.Substring(0, index);
var name = kvp.Substring(index + 1);
var abbrev = kvp[..index];
var name = kvp[(index + 1)..];
map.Add(abbrev, name);
}
return map;
}
public string GetVillager(string name)
{
return VillagerMap.TryGetValue(name, out var result) ? result : name;
}
public string GetVillager(string name) => VillagerMap.GetValueOrDefault(name, name);
public string GetVillagerDefaultPhrase(string name)
{
return VillagerDefaultPhraseMap.TryGetValue(name, out var result) ? result : name; // I know it shouldn't be name but I have to return something
}
public string GetVillagerDefaultPhrase(string name) => VillagerDefaultPhraseMap.GetValueOrDefault(name, name); // I know it shouldn't be `name` but I have to return something
public static string[] GetItemDisplayList(string[] items)
public static string[] GetItemDisplayList(ReadOnlySpan<string> arr)
{
items = (string[])items.Clone();
var items = arr.ToArray();
items[0] = string.Empty;
var set = new HashSet<string>();
for (int i = 0; i < items.Length; i++)
@ -131,10 +126,8 @@ public static string[] GetItemDisplayList(string[] items)
var item = items[i];
if (string.IsNullOrEmpty(item))
items[i] = $"(Item #{i:000})";
else if (set.Contains(item))
else if (!set.Add(item))
items[i] += $" (#{i:000})";
else
set.Add(item);
}
return items;
}
@ -156,7 +149,7 @@ public string GetItemName(Item item)
return $"{display} - {item.Genes}";
}
if (kind == ItemKind.Kind_DIYRecipe || kind == ItemKind.Kind_MessageBottle)
if (kind is ItemKind.Kind_DIYRecipe or ItemKind.Kind_MessageBottle)
{
var display = itemlistdisplay[index];
var recipeID = (ushort)item.FreeParam;
@ -218,13 +211,12 @@ public List<ComboItem> GetAssociatedItems(ushort id, out string baseItemName)
var stringMatch = GetItemName(id);
var index = stringMatch.IndexOf('(');
if (index < 0)
return new List<ComboItem>();
return [];
var search = baseItemName = stringMatch.Substring(0, index);
var search = baseItemName = stringMatch[..index];
if (!string.IsNullOrWhiteSpace(search))
return ItemDataSource.FindAll(x => x.Text.StartsWith(search));
else
return new List<ComboItem>();
return [];
}
public bool HasAssociatedItems(string baseName, out List<ComboItem>? items)
@ -236,20 +228,19 @@ public bool HasAssociatedItems(string baseName, out List<ComboItem>? items)
}
baseName = baseName.Trim().ToLower();
if (!baseName.EndsWith(" "))
if (!baseName.EndsWith(' '))
baseName += " ";
baseName += "(";
items = ItemDataSource.FindAll(x => x.Text.ToLower().StartsWith(baseName));
items = ItemDataSource.FindAll(x => x.Text.StartsWith(baseName, StringComparison.CurrentCultureIgnoreCase));
return items.Count > 0;
}
}
}
public interface IRemakeString
{
public interface IRemakeString
{
IReadOnlyDictionary<string, string> BodyParts { get; }
IReadOnlyDictionary<string, string> BodyColor { get; }
IReadOnlyDictionary<string, string> FabricParts { get; }
IReadOnlyDictionary<string, string> FabricColor { get; }
}
}

View File

@ -1,10 +1,10 @@
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Material used for a <see cref="BridgeType"/> construction (footstep sounds?).
/// </summary>
public enum BridgeMaterial : byte
{
/// <summary>
/// Material used for a <see cref="BridgeType"/> construction (footstep sounds?).
/// </summary>
public enum BridgeMaterial : byte
{
BridgeStone = 0x00,
BridgeSuspension = 0x01,
BridgeJapanese = 0x02,
@ -14,5 +14,4 @@ public enum BridgeMaterial : byte
BridgeReserved = 0x07,
BridgeWood = 0x0A,
BridgeBricks = 0x0B,
}
}

View File

@ -1,10 +1,10 @@
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Bridge model
/// </summary>
public enum BridgeType : ushort
{
/// <summary>
/// Bridge model
/// </summary>
public enum BridgeType : ushort
{
BridgeStone03 = 0x00,
BridgeStone04 = 0x01,
BridgeStone05 = 0x02,
@ -59,5 +59,4 @@ public enum BridgeType : ushort
BridgeBricksDiagonal025 = 0x35,
BridgeBricksDiagonal030 = 0x36,
BridgeBricksDiagonal035 = 0x37,
}
}

View File

@ -1,14 +1,15 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Interact-able structure that can be entered by the player.
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = SIZE, Pack = 1)]
public class Building
{
/// <summary>
/// Interact-able structure that can be entered by the player.
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = SIZE, Pack = 1)]
public class Building
{
public const int SIZE = 0x14;
[field: FieldOffset(0x00)] public BuildingType BuildingType { get; set; }
@ -47,8 +48,7 @@ public void CopyFrom(Building building)
Unused = building.Unused;
}
public static Building[] GetArray(byte[] data) => data.GetArray<Building>(SIZE);
public static Building[] GetArray(ReadOnlySpan<byte> data) => data.GetArray<Building>(SIZE);
public static byte[] SetArray(IReadOnlyList<Building> data) => data.SetArray(SIZE);
public override string ToString() => $"{X:000},{Y:000} - {BuildingType}";
}
}

View File

@ -1,10 +1,10 @@
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Building model &amp; interior that is loaded when the <see cref="Building"/> is entered.
/// </summary>
public enum BuildingType : ushort
{
/// <summary>
/// Building model &amp; interior that is loaded when the <see cref="Building"/> is entered.
/// </summary>
public enum BuildingType : ushort
{
None = 0,
PlayerHouse1 = 1,
PlayerHouse2 = 2,
@ -35,5 +35,4 @@ public enum BuildingType : ushort
Incline = 27,
ReddsTreasureTrawler = 28,
Studio = 29,
}
}

View File

@ -1,10 +1,10 @@
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Door model for a house.
/// </summary>
public enum DoorKind : ushort
{
/// <summary>
/// Door model for a house.
/// </summary>
public enum DoorKind : ushort
{
HouseDoorStandardAR = 0x00,
HouseDoorStandardAS = 0x02,
HouseDoorIronAS = 0x03,
@ -258,5 +258,4 @@ public enum DoorKind : ushort
HouseDoorIronGrillIR = 0x122,
HouseDoorIronGrillJS = 0x123,
HouseDoorIronGrillJR = 0x124,
}
}

View File

@ -1,10 +1,10 @@
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Roof model for a house.
/// </summary>
public enum RoofType : ushort
{
/// <summary>
/// Roof model for a house.
/// </summary>
public enum RoofType : ushort
{
HouseRoofPA04StandardA = 0x00,
HouseRoofPA04StandardB = 0x03,
_1 = 0x04,
@ -171,5 +171,4 @@ public enum RoofType : ushort
HouseRoofNASlateI = 0xAD,
HouseRoofNDWoodI = 0xAE,
HouseRoofNDWoodJ = 0xAF,
}
}

View File

@ -1,10 +1,10 @@
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Incline / Slope model
/// </summary>
public enum SlopeType : ushort
{
/// <summary>
/// Incline / Slope model
/// </summary>
public enum SlopeType : ushort
{
SlopeStoneStair = 0x00,
SlopeIronStair = 0x01,
SlopeWood = 0x02,
@ -14,5 +14,4 @@ public enum SlopeType : ushort
SlopeNatural = 0x1D,
SlopeWoodBlue = 0x1E,
SlopeIronStairBlue = 0x1F,
}
}

View File

@ -1,10 +1,10 @@
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Ambient sound for a house.
/// </summary>
public enum SoundAmbientKind : byte
{
/// <summary>
/// Ambient sound for a house.
/// </summary>
public enum SoundAmbientKind : byte
{
Silence = 0x0,
Rain = 0x1,
Sea = 0x2,
@ -29,5 +29,4 @@ public enum SoundAmbientKind : byte
Healing = 0x1B,
Forest = 0x1C,
Duct = 0x1D,
}
}

View File

@ -1,13 +1,13 @@
using System.Collections.Generic;
using System.Linq;
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Retrieves value metadata for customizing structures.
/// </summary>
public static class StructureUtil
{
/// <summary>
/// Retrieves value metadata for customizing structures.
/// </summary>
public static class StructureUtil
{
public static Dictionary<string, string[]> GetStructureHelpList()
{
var kvpa = new[]
@ -24,5 +24,4 @@ public static class StructureUtil
};
return kvpa.ToDictionary(x => x.Key, x => x.Value);
}
}
}

View File

@ -1,10 +1,10 @@
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Wall model for a house.
/// </summary>
public enum WallType : ushort
{
/// <summary>
/// Wall model for a house.
/// </summary>
public enum WallType : ushort
{
HouseWallPA04StandardA = 0x00,
HouseWallPA04StandardB = 0x05,
HouseWallPA04StandardC = 0x06,
@ -154,5 +154,4 @@ public enum WallType : ushort
HouseWallNAMetalH = 0xAB,
HouseWallNAWoodsidingI = 0xAC,
HouseWallNAWoodsidingJ = 0xAD,
}
}

View File

@ -1,12 +1,14 @@
using System;
using System.Runtime.InteropServices;
using static System.Buffers.Binary.BinaryPrimitives;
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Simple design pattern
/// </summary>
public class DesignPattern : IVillagerOrigin
{
/// <summary>
/// Simple design pattern
/// </summary>
public class DesignPattern : IVillagerOrigin
{
public const int Width = 32;
public const int Height = 32;
@ -19,55 +21,56 @@ public class DesignPattern : IVillagerOrigin
private const int PixelCount = 0x400; // Width * Height
//private const int PixelDataSize = PixelCount / 2; // 4bit|4bit pixel packing
public readonly byte[] Data;
public readonly Memory<byte> Raw;
public Span<byte> Data => Raw.Span;
public DesignPattern(byte[] data) => Data = data;
public DesignPattern(Memory<byte> data) => Raw = data;
public uint Hash
{
get => BitConverter.ToUInt32(Data, 0x00);
set => BitConverter.GetBytes(value).CopyTo(Data, 0x00);
get => ReadUInt32LittleEndian(Data);
set => WriteUInt32LittleEndian(Data, value);
}
public uint Version
{
get => BitConverter.ToUInt32(Data, 0x04);
set => BitConverter.GetBytes(value).CopyTo(Data, 0x04);
get => ReadUInt32LittleEndian(Data[0x04..]);
set => WriteUInt32LittleEndian(Data[0x04..], value);
}
public string DesignName
{
get => StringUtil.GetString(Data, 0x10, 20);
set => StringUtil.GetBytes(value, 20).CopyTo(Data, 0x10);
set => StringUtil.GetBytes(value, 20).CopyTo(Data[0x10..]);
}
public uint TownID
{
get => BitConverter.ToUInt32(Data, PersonalOffset);
set => BitConverter.GetBytes(value).CopyTo(Data, PersonalOffset);
get => ReadUInt32LittleEndian(Data[PersonalOffset..]);
set => WriteUInt32LittleEndian(Data[PersonalOffset..], value);
}
public string TownName
{
get => StringUtil.GetString(Data, PersonalOffset + 0x04, 10);
set => StringUtil.GetBytes(value, 10).CopyTo(Data, PersonalOffset + 0x04);
set => StringUtil.GetBytes(value, 10).CopyTo(Data[(PersonalOffset + 0x04)..]);
}
public byte[] GetTownIdentity() => Data.Slice(PersonalOffset + 0x00, 4 + 20);
public Span<byte> GetTownIdentity() => Data.Slice(PersonalOffset + 0x00, 4 + 20);
public uint PlayerID
{
get => BitConverter.ToUInt32(Data, PersonalOffset + 0x1C);
set => BitConverter.GetBytes(value).CopyTo(Data, PersonalOffset + 0x1C);
get => ReadUInt32LittleEndian(Data[(PersonalOffset + 0x1C)..]);
set => WriteUInt32LittleEndian(Data[(PersonalOffset + 0x1C)..], value);
}
public string PlayerName
{
get => StringUtil.GetString(Data, PersonalOffset + 0x20, 10);
set => StringUtil.GetBytes(value, 10).CopyTo(Data, PersonalOffset + 0x20);
set => StringUtil.GetBytes(value, 10).CopyTo(Data[(PersonalOffset + 0x20)..]);
}
public byte[] GetPlayerIdentity() => Data.Slice(PersonalOffset + 0x1C, 4 + 20);
public Span<byte> GetPlayerIdentity() => Data.Slice(PersonalOffset + 0x1C, 4 + 20);
/// <summary>
/// Gets/Sets the color choice (1-15) for the pixel at the given <see cref="index"/>.
@ -87,7 +90,7 @@ public string PlayerName
var val = Data[ofs];
var update = ((index & 1) == 0)
? (val & 0xF0) | (value & 0xF)
: (value & 0xF) << 4 | (val & 0xF);
: ((value & 0xF) << 4) | (val & 0xF);
Data[ofs] = (byte)update;
}
}
@ -148,5 +151,4 @@ public byte[] GetPaletteBitmap()
}
return result;
}
}
}

View File

@ -1,12 +1,14 @@
using System;
using System.Runtime.InteropServices;
using static System.Buffers.Binary.BinaryPrimitives;
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Advanced design pattern with 4 sheets arranged in a square.
/// </summary>
public class DesignPatternPRO : IVillagerOrigin
{
/// <summary>
/// Advanced design pattern with 4 sheets arranged in a square.
/// </summary>
public class DesignPatternPRO : IVillagerOrigin
{
public const int Width = 32;
public const int Height = 32;
@ -20,55 +22,56 @@ public class DesignPatternPRO : IVillagerOrigin
private const int PixelCount = 0x400; // Width * Height
private const int SheetDataSize = PixelCount / 2; // 4bit|4bit pixel packing
public readonly byte[] Data;
public readonly Memory<byte> Raw;
public Span<byte> Data => Raw.Span;
public DesignPatternPRO(byte[] data) => Data = data;
public DesignPatternPRO(Memory<byte> data) => Raw = data;
public uint Hash
{
get => BitConverter.ToUInt32(Data, 0x00);
set => BitConverter.GetBytes(value).CopyTo(Data, 0x00);
get => ReadUInt32LittleEndian(Data);
set => WriteUInt32LittleEndian(Data, value);
}
public uint Version
{
get => BitConverter.ToUInt32(Data, 0x04);
set => BitConverter.GetBytes(value).CopyTo(Data, 0x04);
get => ReadUInt32LittleEndian(Data[0x04..]);
set => WriteUInt32LittleEndian(Data[0x04..], value);
}
public string DesignName
{
get => StringUtil.GetString(Data, 0x10, 20);
set => StringUtil.GetBytes(value, 20).CopyTo(Data, 0x10);
set => StringUtil.GetBytes(value, 20).CopyTo(Data[0x10..]);
}
public uint TownID
{
get => BitConverter.ToUInt32(Data, PersonalOffset);
set => BitConverter.GetBytes(value).CopyTo(Data, PersonalOffset);
get => ReadUInt32LittleEndian(Data[PersonalOffset..]);
set => WriteUInt32LittleEndian(Data[PersonalOffset..], value);
}
public string TownName
{
get => StringUtil.GetString(Data, PersonalOffset + 0x04, 10);
set => StringUtil.GetBytes(value, 10).CopyTo(Data, PersonalOffset + 0x04);
set => StringUtil.GetBytes(value, 10).CopyTo(Data[(PersonalOffset + 0x04)..]);
}
public byte[] GetTownIdentity() => Data.Slice(PersonalOffset + 0x00, 4 + 20);
public Span<byte> GetTownIdentity() => Data.Slice(PersonalOffset + 0x00, 4 + 20);
public uint PlayerID
{
get => BitConverter.ToUInt32(Data, PersonalOffset + 0x1C);
set => BitConverter.GetBytes(value).CopyTo(Data, PersonalOffset + 0x1C);
get => ReadUInt32LittleEndian(Data[(PersonalOffset + 0x1C)..]);
set => WriteUInt32LittleEndian(Data[(PersonalOffset + 0x1C)..], value);
}
public string PlayerName
{
get => StringUtil.GetString(Data, PersonalOffset + 0x20, 10);
set => StringUtil.GetBytes(value, 10).CopyTo(Data, PersonalOffset + 0x20);
set => StringUtil.GetBytes(value, 10).CopyTo(Data[(PersonalOffset + 0x20)..]);
}
public byte[] GetPlayerIdentity() => Data.Slice(PersonalOffset + 0x1C, 4 + 20);
public Span<byte> GetPlayerIdentity() => Data.Slice(PersonalOffset + 0x1C, 4 + 20);
public void SetPixelAtIndex(int sheet, int index, int value)
{
@ -143,5 +146,4 @@ public byte[] GetPaletteBitmap()
}
return result;
}
}
}

View File

@ -2,14 +2,14 @@
using System.Collections.Generic;
using System.IO;
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Logic for dumping decrypted game files.
/// </summary>
/// <seealso cref="GameFileLoader"/>
public static class GameFileDumper
{
/// <summary>
/// Logic for dumping decrypted game files.
/// </summary>
/// <seealso cref="GameFileLoader"/>
public static class GameFileDumper
{
/// <summary>
/// Dumps a copy of the <see cref="sav"/>'s files in their decrypted state to the requested <see cref="path"/>.
/// </summary>
@ -46,7 +46,7 @@ public static void Dump(this EncryptedFilePair pair, string path)
Dump(path, pair.Data, pair.NameData);
}
private static void Dump(string path, byte[] data, string name)
private static void Dump(string path, ReadOnlySpan<byte> data, string name)
{
Directory.CreateDirectory(path);
var file = Path.Combine(path, name);
@ -84,7 +84,7 @@ public static void DumpPlayerHouses(this HorizonSave sav, string path)
}
}
private static void Dump(this IPlayerHouse h, string path, IVillagerOrigin p) => h.Dump(p.PlayerName, path);
private static void Dump(this IPlayerHouse h, string path, Personal p) => h.Dump(p.PlayerName, path);
private static void Dump(this IPlayerHouse h, string player, string path)
{
@ -276,5 +276,4 @@ private static void Dump(this DesignPatternPRO dp, string path, int index, bool
var dest = Path.Combine(path, fn);
File.WriteAllBytes(dest, dp.Data);
}
}
}

View File

@ -1,13 +1,14 @@
using System.IO;
using System;
using System.IO;
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Logic for loading decrypted game files.
/// </summary>
/// <seealso cref="GameFileDumper"/>
public static class GameFileLoader
{
/// <summary>
/// Logic for loading decrypted game files.
/// </summary>
/// <seealso cref="GameFileDumper"/>
public static class GameFileLoader
{
/// <summary>
/// Loads a copy of the <see cref="sav"/>'s files in their decrypted state from the requested <see cref="path"/>.
/// </summary>
@ -44,7 +45,7 @@ public static void Load(this EncryptedFilePair pair, string path)
Load(path, pair.Data, pair.NameData);
}
private static void Load(string path, byte[] data, string name)
private static void Load(string path, Span<byte> data, string name)
{
if (!Directory.Exists(path))
return;
@ -57,7 +58,6 @@ private static void Load(string path, byte[] data, string name)
if (data.Length != import.Length)
return;
import.CopyTo(data, 0);
}
import.CopyTo(data);
}
}

View File

@ -1,20 +1,20 @@
using System.Collections.Generic;
using System;
namespace NHSE.Core
namespace NHSE.Core;
public static class GameLists
{
public static class GameLists
{
public static readonly IReadOnlyList<ushort> Fruits = new ushort[]
{
public static ReadOnlySpan<ushort> Fruits =>
[
2213, // apple
2287, // Cherry
2214, // Orange
2286, // Peach
2285, // Pear
};
2285 // Pear
];
public static readonly IReadOnlyList<ushort> Bugs = new ushort[]
{
public static ReadOnlySpan<ushort> Bugs =>
[
00582, // brown cicada
00583, // tiger butterfly
00584, // Rajah Brooke's birdwing
@ -99,11 +99,11 @@ public static class GameLists
05157, // giant water bug
05339, // damselfly
05859, // cherry-blossom petal
07374, // maple leaf
};
07374 // maple leaf
];
public static readonly IReadOnlyList<ushort> Fish = new ushort[]
{
public static ReadOnlySpan<ushort> Fish =>
[
00328, // crucian carp
00329, // goldfish
02215, // bitterling
@ -188,11 +188,11 @@ public static class GameLists
04203, // suckerfish
04204, // barreleye
05254, // ranchu goldfish
12514, // water egg
};
12514 // water egg
];
public static readonly IReadOnlyList<ushort> Fossils = new ushort[]
{
public static ReadOnlySpan<ushort> Fossils =>
[
00169, // ankylo skull
00170, // ankylo torso
00171, // ankylo tail
@ -265,11 +265,11 @@ public static class GameLists
04697, // quetzal torso
04698, // right quetzal wing
04699, // left quetzal wing
07251, // diplo tail tip
};
07251 // diplo tail tip
];
public static readonly IReadOnlyList<ushort> Art = new ushort[]
{
public static ReadOnlySpan<ushort> Art =>
[
00002, // scenic painting
00005, // graceful painting (forgery)
00006, // graceful painting
@ -339,11 +339,11 @@ public static class GameLists
12623, // detailed painting (forgery)
12624, // glowing painting
12625, // mysterious painting
12629, // scenic painting (forgery)
};
12629 // scenic painting (forgery)
];
public static readonly IReadOnlyList<ushort> Dive = new ushort[]
{
public static ReadOnlySpan<ushort> Dive =>
[
02620, // seaweed
02830, // sea grapes
02831, // sea urchin
@ -383,11 +383,11 @@ public static class GameLists
07303, // sea pig
07308, // Dungeness crab
07318, // Venus' flower basket
07411, // mussel
};
07411 // mussel
];
public static readonly HashSet<ushort> Shells = new()
{
public static ReadOnlySpan<ushort> Shells =>
[
1374, // sea snail
1375, // venus comb
1376, // conch
@ -398,11 +398,27 @@ public static class GameLists
1382, // cowrie
5982, // summer shell
12968, // pearl
};
12968 // pearl
];
public static readonly HashSet<ushort> Terraforming = new()
{
public static ReadOnlySpan<ushort> Terraforming =>
[
3075, // path construction permit
3247, // waterscaping permit
8773, // stone path permit
8774, // brick path permit
8775, // dark dirt path permit
8776, // arched tile path permit
8777, // sand path permit
8778, // terra-cotta tile permit
8779, // wooden path permit
8780, // waterscaping permit
8781, // cliff construction permit
9771 // custom design path permit
];
public static ReadOnlySpan<ushort> NoCheckReceived =>
[
3075, // path construction permit
3247, // waterscaping permit
8773, // stone path permit
@ -415,10 +431,7 @@ public static class GameLists
8780, // waterscaping permit
8781, // cliff construction permit
9771, // custom design path permit
};
public static readonly HashSet<ushort> NoCheckReceived = new(Terraforming)
{
Item.DIYRecipe,
9046, // Vaulting Pole Recipe
@ -436,7 +449,6 @@ public static class GameLists
12294, // Flimsy Axe Recipe
12327, // Ladder Recipe
};
}
12327 // Ladder Recipe
];
}

View File

@ -1,13 +1,13 @@
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Details for field items -- items with <see cref="Item.ItemId"/> at or above 60,000.
/// </summary>
/// <remarks>
/// These details are extracted from FgMainParam.bcsv
/// </remarks>
public class FieldItemDefinition : INamedValue
{
/// <summary>
/// Details for field items -- items with <see cref="Item.ItemId"/> at or above 60,000.
/// </summary>
/// <remarks>
/// These details are extracted from FgMainParam.bcsv
/// </remarks>
public class FieldItemDefinition : INamedValue
{
/// <summary>
/// Item ID
/// </summary>
@ -51,5 +51,4 @@ public FieldItemDefinition(ushort id, ushort dig, ushort pick, string name, Fiel
/// If the item cannot be picked up, the <see cref="Index"/> is returned as a fallback rather than <see cref="Item.NONE"/>.
/// </remarks>
public ushort HeldItemId => Pick != Item.NONE ? Pick : Dig != Item.NONE ? Dig : Index;
}
}

View File

@ -1,9 +1,9 @@
using static NHSE.Core.FieldItemKind;
namespace NHSE.Core
namespace NHSE.Core;
public enum FieldItemKind : byte
{
public enum FieldItemKind : byte
{
FenceBamboo,
FenceBarbedWire,
FenceChinese,
@ -82,25 +82,27 @@ public enum FieldItemKind : byte
StoneD,
StoneE,
UnitIconHole,
}
}
public static class FieldItemKindExtensions
public static class FieldItemKindExtensions
{
extension(FieldItemKind type)
{
public static bool IsWeed(this FieldItemKind type) => type is >= PltWeedAut0 and <= PltWeedWin1;
public static bool IsPlant(this FieldItemKind type) => type is >= PltFlwAnemone and <= PltWeedWin1;
public static bool IsFence(this FieldItemKind type) => type is >= FenceBamboo and <= FenceWoodWhite;
public static bool IsBush(this FieldItemKind type) => type is >= PltBushAzalea and <= PltBushOsmanthus;
public static bool IsFlower(this FieldItemKind type) => type is >= PltFlwAnemone and <= PltFlwYuri;
public static bool IsTree(this FieldItemKind type) => type is >= PltTreeBamboo and <= PltTreePalm;
public static bool IsStone(this FieldItemKind type) => type is >= StoneA and <= StoneE;
public bool IsWeed => type is (>= PltWeedAut0 and <= PltWeedWin1);
public bool IsPlant => type is (>= PltFlwAnemone and <= PltWeedWin1);
public bool IsFence => type is (>= FenceBamboo and <= FenceWoodWhite);
public bool IsBush => type is (>= PltBushAzalea and <= PltBushOsmanthus);
public bool IsFlower => type is (>= PltFlwAnemone and <= PltFlwYuri);
public bool IsTree => type is (>= PltTreeBamboo and <= PltTreePalm);
public bool IsStone => type is (>= StoneA and <= StoneE);
public static ItemKind ToItemKind(this FieldItemKind type)
public ItemKind ToItemKind()
{
if (type.IsTree())
if (type.IsTree)
return ItemKind.Kind_Tree;
if (type.IsFlower())
if (type.IsFlower)
return ItemKind.Kind_Flower;
if (type.IsWeed())
if (type.IsWeed)
return ItemKind.Kind_Weed;
return ItemKind.Unknown;
}

View File

@ -1,9 +1,9 @@
using System.Collections.Generic;
namespace NHSE.Core
namespace NHSE.Core;
public static class FieldItemList
{
public static class FieldItemList
{
public static ItemKind GetFieldItemKind(ushort id)
{
if (!Items.TryGetValue(id, out var definition))
@ -564,5 +564,4 @@ public static ItemKind GetFieldItemKind(ushort id)
{0xED5E, new FieldItemDefinition(60766, 65534, 12894, "FenceJuneBride6" , FieldItemKind.FenceJuneBride )}, // ウェディングな柵・むらさき
{0xED5F, new FieldItemDefinition(60767, 65534, 12894, "FenceJuneBride5" , FieldItemKind.FenceJuneBride )}, // ウェディングな柵・きいろ
};
}
}

Some files were not shown because too many files have changed in this diff Show More