mirror of
https://github.com/kwsch/NHSE.git
synced 2026-05-02 11:15:21 -05:00
Update to .NET 10 (#683)
This commit is contained in:
parent
63bd5f643c
commit
fa3f0e3cc8
115
.editorconfig
Normal file
115
.editorconfig
Normal 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
|
||||
5
.github/README-es.md
vendored
5
.github/README-es.md
vendored
|
|
@ -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.
|
||||
|
|
|
|||
5
.github/README-fr.md
vendored
5
.github/README-fr.md
vendored
|
|
@ -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.
|
||||
|
|
|
|||
5
.github/README-it.md
vendored
5
.github/README-it.md
vendored
|
|
@ -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).
|
||||
|
|
|
|||
5
.github/README-jp.md
vendored
5
.github/README-jp.md
vendored
|
|
@ -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) を参照してください。
|
||||
|
|
|
|||
4
.github/README-zh-CN.md
vendored
4
.github/README-zh-CN.md
vendored
|
|
@ -10,10 +10,6 @@ NHSE
|
|||
|
||||
可以编辑你从Nintendo Switch中导出的存档。
|
||||
* 请自行解决存档导出问题,本程序并不能直接从你的Switch中导出存档。
|
||||
## 参见
|
||||
|
||||
[MyHorizons](https://github.com/Cuyler36/MyHorizons) by [Cuyler36](https://github.com/Cuyler36/)
|
||||
* 代码的某些部分完全改编自Cuyler36的项目。
|
||||
|
||||
## 其他
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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..];
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 };
|
||||
}
|
||||
}
|
||||
|
|
@ -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 & 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 & 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)..]);
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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..]);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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") { }
|
||||
}
|
||||
}
|
||||
|
|
@ -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") { }
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
|
@ -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..]);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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))..]);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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..]);
|
||||
}
|
||||
|
|
@ -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..]);
|
||||
}
|
||||
|
|
@ -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..]);
|
||||
}
|
||||
|
|
@ -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..]);
|
||||
}
|
||||
|
|
@ -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..]);
|
||||
}
|
||||
|
|
@ -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..]);
|
||||
}
|
||||
|
|
@ -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..]);
|
||||
}
|
||||
|
|
@ -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..]);
|
||||
}
|
||||
|
|
@ -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..]);
|
||||
}
|
||||
|
|
@ -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..]);
|
||||
}
|
||||
|
|
@ -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..]);
|
||||
}
|
||||
|
|
@ -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..]);
|
||||
}
|
||||
|
|
@ -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..]);
|
||||
}
|
||||
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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}";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
namespace NHSE.Core
|
||||
namespace NHSE.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Building model & interior that is loaded when the <see cref="Building"/> is entered.
|
||||
/// </summary>
|
||||
public enum BuildingType : ushort
|
||||
{
|
||||
/// <summary>
|
||||
/// Building model & 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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
];
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue
Block a user