Update to .NET 10 (#683)

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

115
.editorconfig Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,22 +1,21 @@
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)
{
public static readonly byte[] AcreTiles = ResourceUtil.GetBinaryResource("outside.bin");
if (acre > (ushort)OutsideAcre.FldOutNGardenRFront00)
return Color.Transparent.ToArgb();
var baseOfs = acre * 32 * 32 * 4;
public static int GetAcreTileColor(ushort acre, int x, int y)
{
if (acre > (ushort)OutsideAcre.FldOutNGardenRFront00)
return Color.Transparent.ToArgb();
var baseOfs = acre * 32 * 32 * 4;
// 64x64
var shift = (4 * ((y * 64) + x));
var ofs = baseOfs + shift;
var tile = AcreTiles[ofs];
return CollisionUtil.Dict[tile].ToArgb();
}
// 64x64
var shift = (4 * ((y * 64) + x));
var ofs = baseOfs + shift;
var tile = AcreTiles[ofs];
return CollisionUtil.Dict[tile].ToArgb();
}
}
}

View File

@ -1,54 +1,53 @@
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)
{
public static Color GetColor(int index)
{
var arr = Colors;
if ((uint) index < arr.Length)
return arr[index];
var arr = Colors;
if ((uint) index < arr.Length)
return arr[index];
// loop back and blend with something else
index %= arr.Length;
var c = arr[index];
return Blend(Color.Red, c, 0.2f);
}
private static readonly Color[] Colors = new[]
{
0xD9D9D9, 0xCCD9E8, 0x7F7F7F, 0xD5D5D5, 0xF7F7F7, 0xCFCFCF, 0xB4B4B4, 0xF1F1F1,
0xFFFFFF, 0x7F7F7F, 0x7F7F7F, 0xB6B6B6, 0x7FBBEB, 0xFFFFFF, 0x7FB2E5, 0xF9FBFD,
0xDFE6ED, 0x7F7F7F, 0xFFFFF0, 0x7F7F7F, 0xF7F7F7, 0x7F7F7F, 0xE3E3E3, 0xFFFFFF,
0xB1B1B1, 0x7F7F7F, 0xFFFFFF, 0xF7FBFF, 0xFCF5EB, 0x7FFFFF, 0xBFFFE9, 0xF7FFFF,
0xFAFAED, 0xFFF1E1, 0x7F7F7F, 0xFFF5E6, 0x7F7FFF, 0xC495F0, 0xD29494, 0xEEDBC3,
0xAFCECF, 0xBFFF7F, 0xE8B48E, 0xFFBFA7, 0xB1CAF6, 0xFFFBED, 0xED899D, 0x7FFFFF,
0x7F7FC5, 0x7FC5C5, 0xDBC285, 0xD4D4D4, 0x7FB17F, 0xDEDBB5, 0xC57FC5, 0xAAB597,
0xFFC57F, 0xCC98E5, 0xC57F7F, 0xF4CABC, 0xC7DDC5, 0xA39EC5, 0x97A7A7, 0x7FE6E8,
0xC97FE9, 0xFF89C9, 0x7FDFFF, 0xB4B4B4, 0x8EC7FF, 0xD89090, 0xFFFCF7, 0x90C590,
0xFF7FFF, 0xEDEDED, 0xFBFBFF, 0xFFEB7F, 0xECD28F, 0xBFBFBF, 0x7FBF7F, 0xD6FF97,
0xF7FFF7, 0xFFB4D9, 0xE6ADAD, 0xA57FC0, 0xFFFFF7, 0xF7F2C5, 0xF2F2FC, 0xFFF7FA,
0xBDFD7F, 0xFFFCE6, 0xD6EBF2, 0xF7BFBF, 0xEFFFFF, 0xFCFCE8, 0xE9E9E9, 0xC7F6C7,
0xFFDAE0, 0xFFCFBC, 0x8FD8D4, 0xC3E6FC, 0xBBC3CC, 0xD7E1EE, 0xFFFFEF, 0x7FFF7F,
0x98E698, 0xFCF7F2, 0xFF7FFF, 0xBF7F7F, 0xB2E6D4, 0x7F7FE6, 0xDCAAE9, 0xC9B7ED,
0x9DD9B8, 0xBDB3F6, 0x7FFCCC, 0xA3E8E5, 0xE38AC2, 0x8C8CB7, 0xFAFFFC, 0xFFF1F0,
0xFFF1DA, 0xFFEED6, 0x7F7FBF, 0xFEFAF2, 0xBFBF7F, 0xB5C691, 0xFFD27F, 0xFFA27F,
0xECB7EA, 0xF6F3D4, 0xCBFDCB, 0xD7F6F6, 0xEDB7C9, 0xFFF7EA, 0xFFECDC, 0xE6C29F,
0xFFDFE5, 0xEECFEE, 0xD7EFF2, 0xBF7FBF, 0xFF7F7F, 0xDDC7C7, 0xA0B4F0, 0xC5A289,
0xFCBFB8, 0xF9D1AF, 0x96C5AB, 0xFFFAF6, 0xCFA896, 0xDFDFDF, 0xC3E6F5, 0xB4ACE6,
0xB7BFC7, 0xFFFCFC, 0x7FFFBF, 0xA2C0D9, 0xE8D9C5, 0x7FBFBF, 0xEBDFEB, 0xFFB1A3,
0x9FEFE7, 0xF6C0F6, 0xFAEED9, 0xFFFFFF, 0xFAFAFA, 0xFFFF7F, 0xCCE698, 0xF7F7F7,
0xFFFFFF, 0xCFCFCF, 0xDCE8F4, 0xEBF1F8, 0xF7F7F7, 0x99CCFF,
}.Select(z => Color.FromArgb(z | -0x1000000)).ToArray();
public static Color Blend(Color color, Color backColor, double amount)
{
byte r = (byte)((color.R * amount) + (backColor.R * (1 - amount)));
byte g = (byte)((color.G * amount) + (backColor.G * (1 - amount)));
byte b = (byte)((color.B * amount) + (backColor.B * (1 - amount)));
return Color.FromArgb(r, g, b);
}
// loop back and blend with something else
index %= arr.Length;
var c = arr[index];
return Blend(Color.Red, c, 0.2f);
}
}
private static readonly Color[] Colors = new[]
{
0xD9D9D9, 0xCCD9E8, 0x7F7F7F, 0xD5D5D5, 0xF7F7F7, 0xCFCFCF, 0xB4B4B4, 0xF1F1F1,
0xFFFFFF, 0x7F7F7F, 0x7F7F7F, 0xB6B6B6, 0x7FBBEB, 0xFFFFFF, 0x7FB2E5, 0xF9FBFD,
0xDFE6ED, 0x7F7F7F, 0xFFFFF0, 0x7F7F7F, 0xF7F7F7, 0x7F7F7F, 0xE3E3E3, 0xFFFFFF,
0xB1B1B1, 0x7F7F7F, 0xFFFFFF, 0xF7FBFF, 0xFCF5EB, 0x7FFFFF, 0xBFFFE9, 0xF7FFFF,
0xFAFAED, 0xFFF1E1, 0x7F7F7F, 0xFFF5E6, 0x7F7FFF, 0xC495F0, 0xD29494, 0xEEDBC3,
0xAFCECF, 0xBFFF7F, 0xE8B48E, 0xFFBFA7, 0xB1CAF6, 0xFFFBED, 0xED899D, 0x7FFFFF,
0x7F7FC5, 0x7FC5C5, 0xDBC285, 0xD4D4D4, 0x7FB17F, 0xDEDBB5, 0xC57FC5, 0xAAB597,
0xFFC57F, 0xCC98E5, 0xC57F7F, 0xF4CABC, 0xC7DDC5, 0xA39EC5, 0x97A7A7, 0x7FE6E8,
0xC97FE9, 0xFF89C9, 0x7FDFFF, 0xB4B4B4, 0x8EC7FF, 0xD89090, 0xFFFCF7, 0x90C590,
0xFF7FFF, 0xEDEDED, 0xFBFBFF, 0xFFEB7F, 0xECD28F, 0xBFBFBF, 0x7FBF7F, 0xD6FF97,
0xF7FFF7, 0xFFB4D9, 0xE6ADAD, 0xA57FC0, 0xFFFFF7, 0xF7F2C5, 0xF2F2FC, 0xFFF7FA,
0xBDFD7F, 0xFFFCE6, 0xD6EBF2, 0xF7BFBF, 0xEFFFFF, 0xFCFCE8, 0xE9E9E9, 0xC7F6C7,
0xFFDAE0, 0xFFCFBC, 0x8FD8D4, 0xC3E6FC, 0xBBC3CC, 0xD7E1EE, 0xFFFFEF, 0x7FFF7F,
0x98E698, 0xFCF7F2, 0xFF7FFF, 0xBF7F7F, 0xB2E6D4, 0x7F7FE6, 0xDCAAE9, 0xC9B7ED,
0x9DD9B8, 0xBDB3F6, 0x7FFCCC, 0xA3E8E5, 0xE38AC2, 0x8C8CB7, 0xFAFFFC, 0xFFF1F0,
0xFFF1DA, 0xFFEED6, 0x7F7FBF, 0xFEFAF2, 0xBFBF7F, 0xB5C691, 0xFFD27F, 0xFFA27F,
0xECB7EA, 0xF6F3D4, 0xCBFDCB, 0xD7F6F6, 0xEDB7C9, 0xFFF7EA, 0xFFECDC, 0xE6C29F,
0xFFDFE5, 0xEECFEE, 0xD7EFF2, 0xBF7FBF, 0xFF7F7F, 0xDDC7C7, 0xA0B4F0, 0xC5A289,
0xFCBFB8, 0xF9D1AF, 0x96C5AB, 0xFFFAF6, 0xCFA896, 0xDFDFDF, 0xC3E6F5, 0xB4ACE6,
0xB7BFC7, 0xFFFCFC, 0x7FFFBF, 0xA2C0D9, 0xE8D9C5, 0x7FBFBF, 0xEBDFEB, 0xFFB1A3,
0x9FEFE7, 0xF6C0F6, 0xFAEED9, 0xFFFFFF, 0xFAFAFA, 0xFFFF7F, 0xCCE698, 0xF7F7F7,
0xFFFFFF, 0xCFCFCF, 0xDCE8F4, 0xEBF1F8, 0xF7F7F7, 0x99CCFF,
}.Select(z => Color.FromArgb(z | -0x1000000)).ToArray();
public static Color Blend(Color color, Color backColor, double amount)
{
byte r = (byte)((color.R * amount) + (backColor.R * (1 - amount)));
byte g = (byte)((color.G * amount) + (backColor.G * (1 - amount)));
byte b = (byte)((color.B * amount) + (backColor.B * (1 - amount)));
return Color.FromArgb(r, g, b);
}
}

View File

@ -1,79 +1,78 @@
using System.Drawing;
namespace NHSE.Core
namespace NHSE.Core;
public static class FieldItemColor
{
public static class FieldItemColor
public static Color GetItemColor(Item item)
{
public static Color GetItemColor(Item item)
{
if (item.DisplayItemId >= Item.FieldItemMin)
return GetItemColor60000(item);
var kind = ItemInfo.GetItemKind(item);
return ColorUtil.GetColor((int)kind);
}
private static Color GetItemColor60000(Item item)
{
var id = item.DisplayItemId;
if (id == Item.NONE)
return Color.Transparent;
if (!FieldItemList.Items.TryGetValue(id, out var def))
return Color.DarkGreen;
var kind = def.Kind;
if (kind.IsTree())
return GetTreeColor(id);
if (kind.IsFlower())
return Color.HotPink;
if (kind.IsWeed())
return Color.DarkOliveGreen;
if (kind.IsFence())
return Color.LightCoral;
if (kind == FieldItemKind.UnitIconHole)
return Color.Black;
if (kind.IsBush())
return Color.LightGreen;
if (kind.IsStone())
return Color.LightGray;
return Color.DarkGreen; // shouldn't reach here, but ok
}
private static Color GetTreeColor(ushort id)
{
if (0xEC9C <= id && id <= 0xECA0) // money tree
return Color.Gold;
return id switch
{
// Fruit
0xEA61 => Color.Red, // "PltTreeApple"
0xEA62 => Color.Orange, // "PltTreeOrange"
0xEAC8 => Color.Lime, // "PltTreePear"
0xEAC9 => Color.DarkRed, // "PltTreeCherry"
0xEACA => Color.PeachPuff, // "PltTreePeach"
// Cedar
0xEA69 => Color.SaddleBrown, // "PltTreeCedar4"
0xEAB6 => Color.SaddleBrown, // "PltTreeCedar2"
0xEAB7 => Color.SaddleBrown, // "PltTreeCedar1"
0xEAB8 => Color.SaddleBrown, // "PltTreeCedar3"
// Palm
0xEA77 => Color.LightGoldenrodYellow, // "PltTreePalm4"
0xEAC0 => Color.LightGoldenrodYellow, // "PltTreePalm2"
0xEAC1 => Color.LightGoldenrodYellow, // "PltTreePalm1"
0xEAC2 => Color.LightGoldenrodYellow, // "PltTreePalm3"
0xEA76 => Color.MediumSeaGreen, // "PltTreeBamboo4"
0xEAC4 => Color.MediumSeaGreen, // "PltTreeBamboo0"
0xEAC5 => Color.MediumSeaGreen, // "PltTreeBamboo2"
0xEAC6 => Color.MediumSeaGreen, // "PltTreeBamboo1"
0xEAC7 => Color.MediumSeaGreen, // "PltTreeBamboo3"
_ => Color.SandyBrown,
};
}
if (item.DisplayItemId >= Item.FieldItemMin)
return GetItemColor60000(item);
var kind = ItemInfo.GetItemKind(item);
return ColorUtil.GetColor((int)kind);
}
}
private static Color GetItemColor60000(Item item)
{
var id = item.DisplayItemId;
if (id == Item.NONE)
return Color.Transparent;
if (!FieldItemList.Items.TryGetValue(id, out var def))
return Color.DarkGreen;
var kind = def.Kind;
if (kind.IsTree)
return GetTreeColor(id);
if (kind.IsFlower)
return Color.HotPink;
if (kind.IsWeed)
return Color.DarkOliveGreen;
if (kind.IsFence)
return Color.LightCoral;
if (kind == FieldItemKind.UnitIconHole)
return Color.Black;
if (kind.IsBush)
return Color.LightGreen;
if (kind.IsStone)
return Color.LightGray;
return Color.DarkGreen; // shouldn't reach here, but ok
}
private static Color GetTreeColor(ushort id)
{
if (id is >= 0xEC9C and <= 0xECA0) // money tree
return Color.Gold;
return id switch
{
// Fruit
0xEA61 => Color.Red, // "PltTreeApple"
0xEA62 => Color.Orange, // "PltTreeOrange"
0xEAC8 => Color.Lime, // "PltTreePear"
0xEAC9 => Color.DarkRed, // "PltTreeCherry"
0xEACA => Color.PeachPuff, // "PltTreePeach"
// Cedar
0xEA69 => Color.SaddleBrown, // "PltTreeCedar4"
0xEAB6 => Color.SaddleBrown, // "PltTreeCedar2"
0xEAB7 => Color.SaddleBrown, // "PltTreeCedar1"
0xEAB8 => Color.SaddleBrown, // "PltTreeCedar3"
// Palm
0xEA77 => Color.LightGoldenrodYellow, // "PltTreePalm4"
0xEAC0 => Color.LightGoldenrodYellow, // "PltTreePalm2"
0xEAC1 => Color.LightGoldenrodYellow, // "PltTreePalm1"
0xEAC2 => Color.LightGoldenrodYellow, // "PltTreePalm3"
0xEA76 => Color.MediumSeaGreen, // "PltTreeBamboo4"
0xEAC4 => Color.MediumSeaGreen, // "PltTreeBamboo0"
0xEAC5 => Color.MediumSeaGreen, // "PltTreeBamboo2"
0xEAC6 => Color.MediumSeaGreen, // "PltTreeBamboo1"
0xEAC7 => Color.MediumSeaGreen, // "PltTreeBamboo3"
_ => Color.SandyBrown,
};
}
}

View File

@ -1,27 +1,26 @@
using System.Drawing;
namespace NHSE.Core
{
public static class ItemColor
{
public static Color GetItemColor(Item item)
{
if (item.ItemId == Item.NONE)
return Color.Transparent;
var kind = ItemInfo.GetItemKind(item);
if (kind == ItemKind.Unknown)
return Color.LimeGreen;
return ColorUtil.GetColor((int)kind);
}
namespace NHSE.Core;
public static Color GetItemColor(ushort item)
{
if (item == Item.NONE)
return Color.Transparent;
var kind = ItemInfo.GetItemKind(item);
if (kind == ItemKind.Unknown)
return Color.LimeGreen;
return ColorUtil.GetColor((int)kind);
}
public static class ItemColor
{
public static Color GetItemColor(Item item)
{
if (item.ItemId == Item.NONE)
return Color.Transparent;
var kind = ItemInfo.GetItemKind(item);
if (kind == ItemKind.Unknown)
return Color.LimeGreen;
return ColorUtil.GetColor((int)kind);
}
}
public static Color GetItemColor(ushort item)
{
if (item == Item.NONE)
return Color.Transparent;
var kind = ItemInfo.GetItemKind(item);
if (kind == ItemKind.Unknown)
return Color.LimeGreen;
return ColorUtil.GetColor((int)kind);
}
}

View File

@ -3,240 +3,237 @@
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)
{
private static readonly Color River = Color.FromArgb(128, 215, 195);
private static readonly Color Grass = Color.ForestGreen;
if (tile.UnitModelRoad.IsRoad)
return GetRoadColor(tile.UnitModelRoad);
var baseColor = GetTileDefaultColor(tile.UnitModel, tile.LandMakingAngle, relativeX, relativeY);
if (tile.Elevation == 0)
return baseColor;
public static Color GetTileColor(TerrainTile tile, int relativeX, int relativeY)
{
if (tile.UnitModelRoad.IsRoad())
return GetRoadColor(tile.UnitModelRoad);
var baseColor = GetTileDefaultColor(tile.UnitModel, tile.LandMakingAngle, relativeX, relativeY);
if (tile.Elevation == 0)
return baseColor;
return ColorUtil.Blend(baseColor, Color.White, 1.4d / (tile.Elevation + 1));
}
private static Color GetRoadColor(TerrainUnitModel mdl)
{
if (mdl.IsRoadBrick())
return Color.Firebrick;
if (mdl.IsRoadDarkSoil())
return Color.SaddleBrown;
if (mdl.IsRoadSoil())
return Color.Peru;
if (mdl.IsRoadStone())
return Color.DarkGray;
if (mdl.IsRoadPattern())
return Color.Ivory;
if (mdl.IsRoadTile())
return Color.SteelBlue;
if (mdl.IsRoadSand())
return Color.SandyBrown;
return Color.BurlyWood;
}
/// <summary>Notes about rivers the number is how many sides / diagonals are water.</summary>
private static Color GetRiverColor(TerrainUnitModel mdl, LandAngles landAngle, int relativeX, int relativeY)
{
return mdl switch
{
// River0A single "hole" of water land all sides. Rotation does nothing
River0A when (relativeX < 4 || relativeX >= 12 || relativeY < 4 || relativeY >= 12) =>
Grass,
// River1A narrow channel end opening on bottom, land on other sides
River1A => landAngle switch
{
Default when relativeX < 4 || relativeX >= 12 || relativeY < 4 => Grass,
Rotate90ClockAnverse when relativeX < 4 || relativeY < 4 || relativeY >= 12 => Grass,
Rotate180ClockAnverse when relativeX < 4 || relativeX >= 12 || relativeY >= 12 => Grass,
Rotate270ClockAnverse when relativeY < 4 || relativeY >= 12 || relativeX >= 12 => Grass,
_ => River
},
// River2A narrow water channel opening on top and bottom, land left and right
River2A => landAngle switch
{
Default when relativeX is < 4 or >= 12 => Grass,
Rotate90ClockAnverse when relativeY is >= 12 or < 4 => Grass,
Rotate180ClockAnverse when relativeX is < 4 or >= 12 => Grass,
Rotate270ClockAnverse when relativeY is < 4 or >= 12 => Grass,
_ => River
},
// River2B narrow 45 channel angled land top left with nub bottom right
River2B => landAngle switch
{
Default when IsPointInMultiTriangle(relativeX, relativeY, new(4, 15), new(0, 0), new(15, 4), new(0, 15), new(15, 0)) || IsNubOnBottomRight(relativeX, relativeY) || relativeX < 4 || relativeY < 4 => Grass,
Rotate90ClockAnverse when IsPointInMultiTriangle(relativeX, relativeY, new(4, 0), new(0, 15), new(15, 12), new(0, 0), new(15, 15)) || IsNubOnTopRight(relativeX, relativeY) || relativeX < 4 || relativeY >= 12 => Grass,
Rotate180ClockAnverse when IsPointInMultiTriangle(relativeX, relativeY, new(0, 12), new(15, 15), new(12, 0), new(0, 15), new(15, 0)) || IsNubOnTopLeft(relativeX, relativeY) || relativeX >= 12 || relativeY >= 12 => Grass,
Rotate270ClockAnverse when IsPointInMultiTriangle(relativeX, relativeY, new(0, 4), new(15, 0), new(12, 15), new(0, 0), new(15, 15)) || IsNubOnBottomLeft(relativeX, relativeY) || relativeX >= 12 || relativeY < 4 => Grass,
_ => River
},
// River2C narrow 90 channel corner land top left with nub bottom right
River2C => landAngle switch
{
Default when relativeX < 4 || relativeY < 4 || IsNubOnBottomRight(relativeX, relativeY) => Grass,
Rotate90ClockAnverse when relativeX < 4 || relativeY >= 12 || IsNubOnTopRight(relativeX, relativeY) => Grass,
Rotate180ClockAnverse when relativeX >= 12 || relativeY >= 12 || IsNubOnTopLeft(relativeX, relativeY) => Grass,
Rotate270ClockAnverse when relativeX >= 12 || relativeY < 4 || IsNubOnBottomLeft(relativeX, relativeY) => Grass,
_ => River
},
// River3A narrow 3 way land left side, nub top right and bottom right
River3A => landAngle switch
{
Default when relativeX < 4 || IsNubOnTopRight(relativeX, relativeY) || IsNubOnBottomRight(relativeX, relativeY) => Grass,
Rotate90ClockAnverse when relativeY >= 12 || IsNubOnTopLeft(relativeX, relativeY) || IsNubOnTopRight(relativeX, relativeY) => Grass,
Rotate180ClockAnverse when relativeX >= 12 || IsNubOnBottomLeft(relativeX, relativeY) || IsNubOnTopLeft(relativeX, relativeY) => Grass,
Rotate270ClockAnverse when relativeY < 4 || IsNubOnBottomRight(relativeX, relativeY) || IsNubOnBottomLeft(relativeX, relativeY) => Grass,
_ => River
},
// River3B river 45 corner angled land top left, no nub
River3B => landAngle switch
{
Default when IsPointInMultiTriangle(relativeX, relativeY, new(4, 15), new(0, 0), new(15, 4), new(0, 15), new(15, 0)) => Grass,
Rotate90ClockAnverse when IsPointInMultiTriangle(relativeX, relativeY, new(4, 0), new(0, 15), new(15, 12), new(0, 0), new(15, 15)) => Grass,
Rotate180ClockAnverse when IsPointInMultiTriangle(relativeX, relativeY, new(0, 12), new(15, 15), new(12, 0), new(0, 15), new(15, 0)) => Grass,
Rotate270ClockAnverse when IsPointInMultiTriangle(relativeX, relativeY, new(0, 4), new(15, 0), new(12, 15), new(0, 0), new(15, 15)) => Grass,
_ => River
},
// River3C river 90 corner corner land top left, no nub
River3C => landAngle switch
{
Default when relativeX < 4 || relativeY < 4 => Grass,
Rotate90ClockAnverse when relativeX < 4 || relativeY >= 12 => Grass,
Rotate180ClockAnverse when relativeX >= 12 || relativeY >= 12 => Grass,
Rotate270ClockAnverse when relativeX >= 12 || relativeY < 4 => Grass,
_ => River
},
// River4A river side with nub top land left side with nub top right only
River4A => landAngle switch
{
Default when relativeX < 4 || IsNubOnTopRight(relativeX, relativeY) => Grass,
Rotate90ClockAnverse when relativeY >= 12 || IsNubOnTopLeft(relativeX, relativeY) => Grass,
Rotate180ClockAnverse when relativeX >= 12 || IsNubOnBottomLeft(relativeX, relativeY) => Grass,
Rotate270ClockAnverse when relativeY < 4 || IsNubOnBottomRight(relativeX, relativeY) => Grass,
_ => River
},
// River4B river side with nub bottom land left side with nub bottom right only
River4B => landAngle switch
{
Default when relativeX < 4 || IsNubOnBottomRight(relativeX, relativeY) => Grass,
Rotate90ClockAnverse when relativeY >= 12 || IsNubOnTopRight(relativeX, relativeY) => Grass,
Rotate180ClockAnverse when relativeX >= 12 || IsNubOnTopLeft(relativeX, relativeY) => Grass,
Rotate270ClockAnverse when relativeY < 4 || IsNubOnBottomLeft(relativeX, relativeY) => Grass,
_ => River
},
// River4C narrow 4 way nub on all 4 corners, 4 sides water. rotation does nothing
River4C when (IsNubOnBottomLeft(relativeX, relativeY) || IsNubOnBottomRight(relativeX, relativeY) || IsNubOnTopLeft(relativeX, relativeY) || IsNubOnTopRight(relativeX, relativeY)) => Grass,
// River5A river corner to 2 narrow Nub on top left, top right, and bottom right. 2 narrows meet a river
River5A => landAngle switch
{
Default when IsNubOnTopLeft(relativeX, relativeY) || IsNubOnTopRight(relativeX, relativeY) || IsNubOnBottomRight(relativeX, relativeY) => Grass,
Rotate90ClockAnverse when IsNubOnBottomLeft(relativeX, relativeY) || IsNubOnTopLeft(relativeX, relativeY) || IsNubOnTopRight(relativeX, relativeY) => Grass,
Rotate180ClockAnverse when IsNubOnBottomLeft(relativeX, relativeY) || IsNubOnBottomRight(relativeX, relativeY) || IsNubOnTopLeft(relativeX, relativeY) => Grass,
Rotate270ClockAnverse when IsNubOnBottomLeft(relativeX, relativeY) || IsNubOnBottomRight(relativeX, relativeY) || IsNubOnTopRight(relativeX, relativeY) => Grass,
_ => River
},
// River5B river side land on left side
River5B => landAngle switch
{
Default when relativeX < 4 => Grass,
Rotate90ClockAnverse when relativeY >= 12 => Grass,
Rotate180ClockAnverse when relativeX >= 12 => Grass,
Rotate270ClockAnverse when relativeY < 4 => Grass,
_ => River
},
// River6A river 2 opposing nubs nub on top left and bottom right
River6A => landAngle switch
{
Default when IsNubOnTopLeft(relativeX, relativeY) || IsNubOnBottomRight(relativeX, relativeY) => Grass,
Rotate90ClockAnverse when IsNubOnBottomLeft(relativeX, relativeY) || IsNubOnTopRight(relativeX, relativeY) => Grass,
Rotate180ClockAnverse when IsNubOnTopLeft(relativeX, relativeY) || IsNubOnBottomRight(relativeX, relativeY) => Grass,
Rotate270ClockAnverse when IsNubOnBottomLeft(relativeX, relativeY) || IsNubOnTopRight(relativeX, relativeY) => Grass,
_ => River
},
// River6B river 2 nubs same side nub on bottom left and bottom right corner, where 1 narrow meets river bottom side
River6B => landAngle switch
{
Default when IsNubOnBottomLeft(relativeX, relativeY) || IsNubOnBottomRight(relativeX, relativeY) => Grass,
Rotate90ClockAnverse when IsNubOnBottomRight(relativeX, relativeY) || IsNubOnTopRight(relativeX, relativeY) => Grass,
Rotate180ClockAnverse when IsNubOnTopRight(relativeX, relativeY) || IsNubOnTopLeft(relativeX, relativeY) => Grass,
Rotate270ClockAnverse when IsNubOnTopLeft(relativeX, relativeY) || IsNubOnBottomLeft(relativeX, relativeY) => Grass,
_ => River
},
// River7A river 1 nub nub on bottom left corner, fills gaps of diagonal bank
River7A => landAngle switch
{
Default when IsNubOnBottomLeft(relativeX, relativeY) => Grass,
Rotate90ClockAnverse when IsNubOnBottomRight(relativeX, relativeY) => Grass,
Rotate180ClockAnverse when IsNubOnTopRight(relativeX, relativeY) => Grass,
Rotate270ClockAnverse when IsNubOnTopLeft(relativeX, relativeY) => Grass,
_ => River
},
// River8A river is no land, just water. Rotation doesn't matter
River8A => River,
_ => River
};
}
private static bool IsNubOnTopLeft(int relativeX, int relativeY) => IsPointInTriangle(relativeX, relativeY, new(0, 4), new(0, 0), new(4, 0));
private static bool IsNubOnTopRight(int relativeX, int relativeY) => IsPointInTriangle(relativeX, relativeY, new(12, 0), new(15, 0), new(15, 4));
private static bool IsNubOnBottomLeft(int relativeX, int relativeY) => IsPointInTriangle(relativeX, relativeY, new(0, 12), new(0, 15), new(4, 15));
private static bool IsNubOnBottomRight(int relativeX, int relativeY) => IsPointInTriangle(relativeX, relativeY, new(12, 15), new(15, 15), new(15, 12));
private static bool IsPointInMultiTriangle(int px, int py, Coordinate a, Coordinate b, Coordinate c, Coordinate vortexA, Coordinate vortexB)
{
return IsPointInTriangle(px, py, a, vortexA, b)
|| IsPointInTriangle(px, py, a, b, c)
|| IsPointInTriangle(px, py, c, b, vortexB);
}
private static bool IsPointInTriangle(int px, int py, Coordinate a, Coordinate b, Coordinate c)
{
Coordinate p = new(px, py);
float areaTotal = GetTriangleArea(a, b, c);
float area1 = GetTriangleArea(p, b, c);
float area2 = GetTriangleArea(a, p, c);
float area3 = GetTriangleArea(a, b, p);
return Math.Abs(areaTotal - (area1 + area2 + area3)) < 0.0001f;
}
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);
}
private readonly record struct Coordinate(int X, int Y);
private static readonly Color CliffBase = ColorUtil.Blend(Grass, Color.Black, 0.6d);
private static Color GetTileDefaultColor(TerrainUnitModel mdl, ushort landAngle, int relativeX, int relativeY)
{
var angle = (LandAngles)landAngle;
if (mdl.IsRiver())
return GetRiverColor(mdl, angle, relativeX, relativeY);
if (mdl.IsFall())
return Color.DeepSkyBlue;
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);
if (num < 0)
return name;
return name.Substring(0, num) + Environment.NewLine + name.Substring(num);
}
return ColorUtil.Blend(baseColor, Color.White, 1.4d / (tile.Elevation + 1));
}
}
private static Color GetRoadColor(TerrainUnitModel mdl)
{
if (mdl.IsRoadBrick)
return Color.Firebrick;
if (mdl.IsRoadDarkSoil)
return Color.SaddleBrown;
if (mdl.IsRoadSoil)
return Color.Peru;
if (mdl.IsRoadStone)
return Color.DarkGray;
if (mdl.IsRoadPattern)
return Color.Ivory;
if (mdl.IsRoadTile)
return Color.SteelBlue;
if (mdl.IsRoadSand)
return Color.SandyBrown;
return Color.BurlyWood;
}
/// <summary>Notes about rivers the number is how many sides / diagonals are water.</summary>
private static Color GetRiverColor(TerrainUnitModel mdl, LandAngles landAngle, int relativeX, int relativeY)
{
return mdl switch
{
// River0A single "hole" of water land all sides. Rotation does nothing
River0A when (relativeX < 4 || relativeX >= 12 || relativeY < 4 || relativeY >= 12) =>
Grass,
// River1A narrow channel end opening on bottom, land on other sides
River1A => landAngle switch
{
Default when relativeX < 4 || relativeX >= 12 || relativeY < 4 => Grass,
Rotate90ClockAnverse when relativeX < 4 || relativeY < 4 || relativeY >= 12 => Grass,
Rotate180ClockAnverse when relativeX < 4 || relativeX >= 12 || relativeY >= 12 => Grass,
Rotate270ClockAnverse when relativeY < 4 || relativeY >= 12 || relativeX >= 12 => Grass,
_ => River
},
// River2A narrow water channel opening on top and bottom, land left and right
River2A => landAngle switch
{
Default when relativeX is < 4 or >= 12 => Grass,
Rotate90ClockAnverse when relativeY is >= 12 or < 4 => Grass,
Rotate180ClockAnverse when relativeX is < 4 or >= 12 => Grass,
Rotate270ClockAnverse when relativeY is < 4 or >= 12 => Grass,
_ => River
},
// River2B narrow 45 channel angled land top left with nub bottom right
River2B => landAngle switch
{
Default when IsPointInMultiTriangle(relativeX, relativeY, new(4, 15), new(0, 0), new(15, 4), new(0, 15), new(15, 0)) || IsNubOnBottomRight(relativeX, relativeY) || relativeX < 4 || relativeY < 4 => Grass,
Rotate90ClockAnverse when IsPointInMultiTriangle(relativeX, relativeY, new(4, 0), new(0, 15), new(15, 12), new(0, 0), new(15, 15)) || IsNubOnTopRight(relativeX, relativeY) || relativeX < 4 || relativeY >= 12 => Grass,
Rotate180ClockAnverse when IsPointInMultiTriangle(relativeX, relativeY, new(0, 12), new(15, 15), new(12, 0), new(0, 15), new(15, 0)) || IsNubOnTopLeft(relativeX, relativeY) || relativeX >= 12 || relativeY >= 12 => Grass,
Rotate270ClockAnverse when IsPointInMultiTriangle(relativeX, relativeY, new(0, 4), new(15, 0), new(12, 15), new(0, 0), new(15, 15)) || IsNubOnBottomLeft(relativeX, relativeY) || relativeX >= 12 || relativeY < 4 => Grass,
_ => River
},
// River2C narrow 90 channel corner land top left with nub bottom right
River2C => landAngle switch
{
Default when relativeX < 4 || relativeY < 4 || IsNubOnBottomRight(relativeX, relativeY) => Grass,
Rotate90ClockAnverse when relativeX < 4 || relativeY >= 12 || IsNubOnTopRight(relativeX, relativeY) => Grass,
Rotate180ClockAnverse when relativeX >= 12 || relativeY >= 12 || IsNubOnTopLeft(relativeX, relativeY) => Grass,
Rotate270ClockAnverse when relativeX >= 12 || relativeY < 4 || IsNubOnBottomLeft(relativeX, relativeY) => Grass,
_ => River
},
// River3A narrow 3 way land left side, nub top right and bottom right
River3A => landAngle switch
{
Default when relativeX < 4 || IsNubOnTopRight(relativeX, relativeY) || IsNubOnBottomRight(relativeX, relativeY) => Grass,
Rotate90ClockAnverse when relativeY >= 12 || IsNubOnTopLeft(relativeX, relativeY) || IsNubOnTopRight(relativeX, relativeY) => Grass,
Rotate180ClockAnverse when relativeX >= 12 || IsNubOnBottomLeft(relativeX, relativeY) || IsNubOnTopLeft(relativeX, relativeY) => Grass,
Rotate270ClockAnverse when relativeY < 4 || IsNubOnBottomRight(relativeX, relativeY) || IsNubOnBottomLeft(relativeX, relativeY) => Grass,
_ => River
},
// River3B river 45 corner angled land top left, no nub
River3B => landAngle switch
{
Default when IsPointInMultiTriangle(relativeX, relativeY, new(4, 15), new(0, 0), new(15, 4), new(0, 15), new(15, 0)) => Grass,
Rotate90ClockAnverse when IsPointInMultiTriangle(relativeX, relativeY, new(4, 0), new(0, 15), new(15, 12), new(0, 0), new(15, 15)) => Grass,
Rotate180ClockAnverse when IsPointInMultiTriangle(relativeX, relativeY, new(0, 12), new(15, 15), new(12, 0), new(0, 15), new(15, 0)) => Grass,
Rotate270ClockAnverse when IsPointInMultiTriangle(relativeX, relativeY, new(0, 4), new(15, 0), new(12, 15), new(0, 0), new(15, 15)) => Grass,
_ => River
},
// River3C river 90 corner corner land top left, no nub
River3C => landAngle switch
{
Default when relativeX < 4 || relativeY < 4 => Grass,
Rotate90ClockAnverse when relativeX < 4 || relativeY >= 12 => Grass,
Rotate180ClockAnverse when relativeX >= 12 || relativeY >= 12 => Grass,
Rotate270ClockAnverse when relativeX >= 12 || relativeY < 4 => Grass,
_ => River
},
// River4A river side with nub top land left side with nub top right only
River4A => landAngle switch
{
Default when relativeX < 4 || IsNubOnTopRight(relativeX, relativeY) => Grass,
Rotate90ClockAnverse when relativeY >= 12 || IsNubOnTopLeft(relativeX, relativeY) => Grass,
Rotate180ClockAnverse when relativeX >= 12 || IsNubOnBottomLeft(relativeX, relativeY) => Grass,
Rotate270ClockAnverse when relativeY < 4 || IsNubOnBottomRight(relativeX, relativeY) => Grass,
_ => River
},
// River4B river side with nub bottom land left side with nub bottom right only
River4B => landAngle switch
{
Default when relativeX < 4 || IsNubOnBottomRight(relativeX, relativeY) => Grass,
Rotate90ClockAnverse when relativeY >= 12 || IsNubOnTopRight(relativeX, relativeY) => Grass,
Rotate180ClockAnverse when relativeX >= 12 || IsNubOnTopLeft(relativeX, relativeY) => Grass,
Rotate270ClockAnverse when relativeY < 4 || IsNubOnBottomLeft(relativeX, relativeY) => Grass,
_ => River
},
// River4C narrow 4 way nub on all 4 corners, 4 sides water. rotation does nothing
River4C when (IsNubOnBottomLeft(relativeX, relativeY) || IsNubOnBottomRight(relativeX, relativeY) || IsNubOnTopLeft(relativeX, relativeY) || IsNubOnTopRight(relativeX, relativeY)) => Grass,
// River5A river corner to 2 narrow Nub on top left, top right, and bottom right. 2 narrows meet a river
River5A => landAngle switch
{
Default when IsNubOnTopLeft(relativeX, relativeY) || IsNubOnTopRight(relativeX, relativeY) || IsNubOnBottomRight(relativeX, relativeY) => Grass,
Rotate90ClockAnverse when IsNubOnBottomLeft(relativeX, relativeY) || IsNubOnTopLeft(relativeX, relativeY) || IsNubOnTopRight(relativeX, relativeY) => Grass,
Rotate180ClockAnverse when IsNubOnBottomLeft(relativeX, relativeY) || IsNubOnBottomRight(relativeX, relativeY) || IsNubOnTopLeft(relativeX, relativeY) => Grass,
Rotate270ClockAnverse when IsNubOnBottomLeft(relativeX, relativeY) || IsNubOnBottomRight(relativeX, relativeY) || IsNubOnTopRight(relativeX, relativeY) => Grass,
_ => River
},
// River5B river side land on left side
River5B => landAngle switch
{
Default when relativeX < 4 => Grass,
Rotate90ClockAnverse when relativeY >= 12 => Grass,
Rotate180ClockAnverse when relativeX >= 12 => Grass,
Rotate270ClockAnverse when relativeY < 4 => Grass,
_ => River
},
// River6A river 2 opposing nubs nub on top left and bottom right
River6A => landAngle switch
{
Default when IsNubOnTopLeft(relativeX, relativeY) || IsNubOnBottomRight(relativeX, relativeY) => Grass,
Rotate90ClockAnverse when IsNubOnBottomLeft(relativeX, relativeY) || IsNubOnTopRight(relativeX, relativeY) => Grass,
Rotate180ClockAnverse when IsNubOnTopLeft(relativeX, relativeY) || IsNubOnBottomRight(relativeX, relativeY) => Grass,
Rotate270ClockAnverse when IsNubOnBottomLeft(relativeX, relativeY) || IsNubOnTopRight(relativeX, relativeY) => Grass,
_ => River
},
// River6B river 2 nubs same side nub on bottom left and bottom right corner, where 1 narrow meets river bottom side
River6B => landAngle switch
{
Default when IsNubOnBottomLeft(relativeX, relativeY) || IsNubOnBottomRight(relativeX, relativeY) => Grass,
Rotate90ClockAnverse when IsNubOnBottomRight(relativeX, relativeY) || IsNubOnTopRight(relativeX, relativeY) => Grass,
Rotate180ClockAnverse when IsNubOnTopRight(relativeX, relativeY) || IsNubOnTopLeft(relativeX, relativeY) => Grass,
Rotate270ClockAnverse when IsNubOnTopLeft(relativeX, relativeY) || IsNubOnBottomLeft(relativeX, relativeY) => Grass,
_ => River
},
// River7A river 1 nub nub on bottom left corner, fills gaps of diagonal bank
River7A => landAngle switch
{
Default when IsNubOnBottomLeft(relativeX, relativeY) => Grass,
Rotate90ClockAnverse when IsNubOnBottomRight(relativeX, relativeY) => Grass,
Rotate180ClockAnverse when IsNubOnTopRight(relativeX, relativeY) => Grass,
Rotate270ClockAnverse when IsNubOnTopLeft(relativeX, relativeY) => Grass,
_ => River
},
// River8A river is no land, just water. Rotation doesn't matter
River8A => River,
_ => River
};
}
private static bool IsNubOnTopLeft(int relativeX, int relativeY) => IsPointInTriangle(relativeX, relativeY, new(0, 4), new(0, 0), new(4, 0));
private static bool IsNubOnTopRight(int relativeX, int relativeY) => IsPointInTriangle(relativeX, relativeY, new(12, 0), new(15, 0), new(15, 4));
private static bool IsNubOnBottomLeft(int relativeX, int relativeY) => IsPointInTriangle(relativeX, relativeY, new(0, 12), new(0, 15), new(4, 15));
private static bool IsNubOnBottomRight(int relativeX, int relativeY) => IsPointInTriangle(relativeX, relativeY, new(12, 15), new(15, 15), new(15, 12));
private static bool IsPointInMultiTriangle(int px, int py, Coordinate a, Coordinate b, Coordinate c, Coordinate vortexA, Coordinate vortexB)
{
return IsPointInTriangle(px, py, a, vortexA, b)
|| IsPointInTriangle(px, py, a, b, c)
|| IsPointInTriangle(px, py, c, b, vortexB);
}
private static bool IsPointInTriangle(int px, int py, Coordinate a, Coordinate b, Coordinate c)
{
Coordinate p = new(px, py);
float areaTotal = GetTriangleArea(a, b, c);
float area1 = GetTriangleArea(p, b, c);
float area2 = GetTriangleArea(a, p, c);
float area3 = GetTriangleArea(a, b, p);
return Math.Abs(areaTotal - (area1 + area2 + area3)) < 0.0001f;
}
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);
}
private readonly record struct Coordinate(int X, int Y);
private static readonly Color CliffBase = ColorUtil.Blend(Grass, Color.Black, 0.6d);
private static Color GetTileDefaultColor(TerrainUnitModel mdl, ushort landAngle, int relativeX, int relativeY)
{
var angle = (LandAngles)landAngle;
if (mdl.IsRiver)
return GetRiverColor(mdl, angle, relativeX, relativeY);
if (mdl.IsFall)
return Color.DeepSkyBlue;
if (mdl.IsCliff)
return CliffBase;
return Grass;
}
public static string GetTileName(TerrainTile tile)
{
var name = tile.UnitModel.ToString();
var num = name.IndexOfAnyInRange('0', '9');
if (num < 0)
return name;
return name[..num] + Environment.NewLine + name[num..];
}
}

View File

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

View File

@ -2,86 +2,82 @@
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
{
private int Modified { get; set; }
private int Iterated { get; set; }
private int Failed { get; set; }
protected abstract bool CanModify(T item);
protected abstract bool Finalize(T item);
/// <summary>
/// Carries out a batch edit and contains information summarizing the results.
/// Tries to modify the <see cref="item"/>.
/// </summary>
public abstract class BatchProcessor<T> where T : class
/// <param name="item">Object to modify.</param>
/// <param name="filters">Filters which must be satisfied prior to any modifications being made.</param>
/// <param name="modifications">Modifications to perform on the item.</param>
/// <returns>Result of the attempted modification.</returns>
public bool Process(T item, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications)
{
private int Modified { get; set; }
private int Iterated { get; set; }
private int Failed { get; set; }
if (!CanModify(item))
return false;
protected readonly BatchMutator<T> Mutator;
protected BatchProcessor(BatchMutator<T> mut) => Mutator = mut;
var result = Mutator.Modify(item, filters, modifications);
if (result != ModifyResult.Invalid)
Iterated++;
if (result == ModifyResult.Error)
Failed++;
if (result != ModifyResult.Modified)
return false;
protected abstract bool CanModify(T item);
protected abstract bool Finalize(T item);
Finalize(item);
Modified++;
return true;
}
/// <summary>
/// Tries to modify the <see cref="item"/>.
/// </summary>
/// <param name="item">Object to modify.</param>
/// <param name="filters">Filters which must be satisfied prior to any modifications being made.</param>
/// <param name="modifications">Modifications to perform on the item.</param>
/// <returns>Result of the attempted modification.</returns>
public bool Process(T item, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications)
/// <summary>
/// Gets a message indicating the overall result of all modifications performed across multiple Batch Edit jobs.
/// </summary>
/// <param name="sets">Collection of modifications.</param>
/// <returns>Friendly (multi-line) string indicating the result of the batch edits.</returns>
public string GetEditorResults(ICollection<StringInstructionSet> sets)
{
if (sets.Count == 0)
return "No instructions present.";
int ctr = Modified / sets.Count;
int len = Iterated / sets.Count;
string maybe = sets.Count == 1 ? string.Empty : "~";
string result = $"Success: {maybe}{ctr}/{len}";
if (Failed > 0)
result += Environment.NewLine + maybe + $"Failed: {Failed} not processed.";
return result;
}
public void Execute(ReadOnlySpan<string> lines, IEnumerable<T> data)
{
var sets = StringInstructionSet.GetBatchSets(lines).ToArray();
foreach (var pk in data)
{
if (!CanModify(item))
return false;
var result = Mutator.Modify(item, filters, modifications);
if (result != ModifyResult.Invalid)
Iterated++;
if (result == ModifyResult.Error)
Failed++;
if (result != ModifyResult.Modified)
return false;
Finalize(item);
Modified++;
return true;
}
/// <summary>
/// Gets a message indicating the overall result of all modifications performed across multiple Batch Edit jobs.
/// </summary>
/// <param name="sets">Collection of modifications.</param>
/// <returns>Friendly (multi-line) string indicating the result of the batch edits.</returns>
public string GetEditorResults(ICollection<StringInstructionSet> sets)
{
if (sets.Count == 0)
return "No instructions present.";
int ctr = Modified / sets.Count;
int len = Iterated / sets.Count;
string maybe = sets.Count == 1 ? string.Empty : "~";
string result = $"Success: {maybe}{ctr}/{len}";
if (Failed > 0)
result += Environment.NewLine + maybe + $"Failed: {Failed} not processed.";
return result;
}
public void Execute(IList<string> lines, IEnumerable<T> data)
{
var sets = StringInstructionSet.GetBatchSets(lines).ToArray();
foreach (var pk in data)
{
foreach (var set in sets)
Process(pk, set.Filters, set.Instructions);
}
}
protected abstract void Initialize(StringInstructionSet[] sets);
public void Process(StringInstructionSet[] sets, IReadOnlyList<T> items)
{
Initialize(sets);
foreach (var s in sets)
{
foreach (var i in items)
Process(i, s.Filters, s.Instructions);
}
foreach (var set in sets)
Process(pk, set.Filters, set.Instructions);
}
}
}
protected abstract void Initialize(ReadOnlySpan<StringInstructionSet> sets);
public void Process(ReadOnlySpan<StringInstructionSet> sets, IReadOnlyList<T> items)
{
Initialize(sets);
foreach (var s in sets)
{
foreach (var i in items)
Process(i, s.Filters, s.Instructions);
}
}
}

View File

@ -5,205 +5,214 @@
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)
{
public readonly ItemReflection Reflect = ItemReflection.Default;
public override ModifyResult Modify(Item item, IEnumerable<StringInstruction> filters, IEnumerable<StringInstruction> modifications)
var pi = Reflect.Props[Array.IndexOf(Reflect.Types, item.GetType())];
foreach (var cmd in filters)
{
var pi = Reflect.Props[Array.IndexOf(Reflect.Types, item.GetType())];
foreach (var cmd in filters)
try
{
try
{
if (!IsFilterMatch(cmd, item, pi))
return ModifyResult.Filtered;
}
// Swallow any error because this can be malformed user input.
catch (Exception ex)
{
Debug.WriteLine($"Failed to compare: {ex.Message} - {cmd.PropertyName} {cmd.PropertyValue}");
return ModifyResult.Error;
}
if (!IsFilterMatch(cmd, item, pi))
return ModifyResult.Filtered;
}
ModifyResult result = ModifyResult.Modified;
foreach (var cmd in modifications)
// Swallow any error because this can be malformed user input.
catch (Exception ex)
{
try
{
var tmp = SetProperty(cmd, item, pi);
if (tmp != ModifyResult.Modified)
result = tmp;
}
// Swallow any error because this can be malformed user input.
catch (Exception ex)
{
Debug.WriteLine($"Failed to modify: {ex.Message} - {cmd.PropertyName} {cmd.PropertyValue}");
}
Debug.WriteLine($"Failed to compare: {ex.Message} - {cmd.PropertyName} {cmd.PropertyValue}");
return ModifyResult.Error;
}
return result;
}
/// <summary>
/// Sets the 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)
ModifyResult result = ModifyResult.Modified;
foreach (var cmd in modifications)
{
if (SetComplexProperty(item, cmd))
return ModifyResult.Modified;
try
{
var tmp = SetProperty(cmd, item, pi);
if (tmp != ModifyResult.Modified)
result = tmp;
}
// Swallow any error because this can be malformed user input.
catch (Exception ex)
{
Debug.WriteLine($"Failed to modify: {ex.Message} - {cmd.PropertyName} {cmd.PropertyValue}");
}
}
return result;
}
if (!props.TryGetValue(cmd.PropertyName, out var pi))
return ModifyResult.Error;
if (!pi.CanWrite)
return ModifyResult.Error;
object val = cmd.Random ? (object)cmd.RandomValue : cmd.PropertyValue;
ReflectUtil.SetValue(pi, item, val);
/// <summary>
/// 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, Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>> props)
{
if (SetComplexProperty(item, cmd))
return ModifyResult.Modified;
if (!props.TryGetValue(cmd.PropertyName, out var pi))
return ModifyResult.Error;
if (!pi.CanWrite)
return ModifyResult.Error;
if (cmd.Random)
ReflectUtil.SetValue(pi, item, cmd.RandomValue);
else
ReflectUtil.SetValue(pi, item, cmd.PropertyValue);
return ModifyResult.Modified;
}
private static bool SetComplexProperty(Item item, StringInstruction cmd)
{
// Zeroed out item?
if (cmd.PropertyName == nameof(Item.ItemId))
{
if (!int.TryParse(cmd.PropertyValue, out var val))
return false;
if (val is not (0 or 0xFFFE))
return false;
item.Delete();
return true;
}
return false;
}
/// <summary>
/// Tries to fetch the <see cref="Item"/> property from the cache of available properties.
/// </summary>
/// <param name="item">Pokémon to check</param>
/// <param name="name">Property Name to check</param>
/// <param name="pi">Property Info retrieved (if any).</param>
/// <returns>True if has property, false if does not.</returns>
public bool TryGetHasProperty(Item item, string name, [NotNullWhen(true)] out PropertyInfo? pi)
{
var type = item.GetType();
return TryGetHasProperty(type, name, out pi);
}
/// <summary>
/// Tries to fetch the <see cref="Item"/> property from the cache of available properties.
/// </summary>
/// <param name="type">Type to check</param>
/// <param name="name">Property Name to check</param>
/// <param name="pi">Property Info retrieved (if any).</param>
/// <returns>True if has property, false if does not.</returns>
public bool TryGetHasProperty(Type type, string name, [NotNullWhen(true)] out PropertyInfo? pi)
{
var index = Array.IndexOf(Reflect.Types, type);
if (index < 0)
{
pi = null;
return false;
}
var props = Reflect.Props[index];
return props.TryGetValue(name, out pi);
}
/// <summary>
/// Gets the type of the <see cref="Item"/> property using the saved cache of properties.
/// </summary>
/// <param name="propertyName">Property Name to fetch the type for</param>
/// <param name="typeIndex">Type index. Leave empty (0) for a nonspecific format.</param>
/// <returns>Short name of the property's type.</returns>
public string? GetPropertyType(string propertyName, int typeIndex = 0)
{
if (typeIndex == 0) // Any
{
foreach (var p in Reflect.Props)
{
if (p.TryGetValue(propertyName, out var pi))
return pi.PropertyType.Name;
}
return null;
}
private static bool SetComplexProperty(Item item, StringInstruction cmd)
int index = typeIndex - 1 >= Reflect.Props.Length ? 0 : typeIndex - 1; // All vs Specific
var pr = Reflect.Props[index];
if (!pr.TryGetValue(propertyName, out var info))
return null;
return info.PropertyType.Name;
}
/// <summary>
/// Checks if the object is filtered by the provided <see cref="filters"/>.
/// </summary>
/// <param name="filters">Filters which must be satisfied.</param>
/// <param name="item">Object to check.</param>
/// <returns>True if <see cref="item"/> matches all filters.</returns>
public bool IsFilterMatch(IEnumerable<StringInstruction> filters, Item item) => filters.All(z => IsFilterMatch(z, item, Reflect.Props[Array.IndexOf(Reflect.Types, item.GetType())]));
/// <summary>
/// Checks 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 filter matches, else false.</returns>
private static bool IsFilterMatch(StringInstruction cmd, Item item, Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>> props)
{
return IsPropertyFiltered(cmd, item, props);
}
/// <summary>
/// Checks 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</param>
/// <returns>True if filtered, else false.</returns>
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;
var val = cmd.PropertyValue;
if (val.StartsWith(CONST_POINTER) && props.TryGetValue(val.AsSpan(1), out var opi))
{
// Zeroed out item?
if (cmd.PropertyName == nameof(Item.ItemId))
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>
/// Checks if the object is filtered by the provided <see cref="filters"/>.
/// </summary>
/// <param name="filters">Filters which must be satisfied.</param>
/// <param name="obj">Object to check.</param>
/// <returns>True if <see cref="obj"/> matches all filters.</returns>
public static bool IsFilterMatch(IEnumerable<StringInstruction> filters, object obj)
{
foreach (var cmd in filters)
{
if (!ReflectUtil.HasProperty(obj, cmd.PropertyName, out var pi))
return false;
try
{
if (!int.TryParse(cmd.PropertyValue, out var val))
return false;
if (val is not 0 or 0xFFFE)
return false;
item.Delete();
return true;
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.
catch (Exception e)
{
Debug.WriteLine($"Unable to compare {cmd.PropertyName} to {cmd.PropertyValue}.");
Debug.WriteLine(e.Message);
}
return false;
}
/// <summary>
/// Tries to fetch the <see cref="Item"/> property from the cache of available properties.
/// </summary>
/// <param name="item">Pokémon to check</param>
/// <param name="name">Property Name to check</param>
/// <param name="pi">Property Info retrieved (if any).</param>
/// <returns>True if has property, false if does not.</returns>
public bool TryGetHasProperty(Item item, string name, [NotNullWhen(true)] out PropertyInfo? pi)
{
var type = item.GetType();
return TryGetHasProperty(type, name, out pi);
}
/// <summary>
/// Tries to fetch the <see cref="Item"/> property from the cache of available properties.
/// </summary>
/// <param name="type">Type to check</param>
/// <param name="name">Property Name to check</param>
/// <param name="pi">Property Info retrieved (if any).</param>
/// <returns>True if has property, false if does not.</returns>
public bool TryGetHasProperty(Type type, string name, [NotNullWhen(true)] out PropertyInfo? pi)
{
var index = Array.IndexOf(Reflect.Types, type);
if (index < 0)
{
pi = null;
return false;
}
var props = Reflect.Props[index];
return props.TryGetValue(name, out pi);
}
/// <summary>
/// Gets the type of the <see cref="Item"/> property using the saved cache of properties.
/// </summary>
/// <param name="propertyName">Property Name to fetch the type for</param>
/// <param name="typeIndex">Type index. Leave empty (0) for a nonspecific format.</param>
/// <returns>Short name of the property's type.</returns>
public string? GetPropertyType(string propertyName, int typeIndex = 0)
{
if (typeIndex == 0) // Any
{
foreach (var p in Reflect.Props)
{
if (p.TryGetValue(propertyName, out var pi))
return pi.PropertyType.Name;
}
return null;
}
int index = typeIndex - 1 >= Reflect.Props.Length ? 0 : typeIndex - 1; // All vs Specific
var pr = Reflect.Props[index];
if (!pr.TryGetValue(propertyName, out var info))
return null;
return info.PropertyType.Name;
}
/// <summary>
/// Checks if the object is filtered by the provided <see cref="filters"/>.
/// </summary>
/// <param name="filters">Filters which must be satisfied.</param>
/// <param name="item">Object to check.</param>
/// <returns>True if <see cref="item"/> matches all filters.</returns>
public bool IsFilterMatch(IEnumerable<StringInstruction> filters, Item item) => filters.All(z => IsFilterMatch(z, item, Reflect.Props[Array.IndexOf(Reflect.Types, item.GetType())]));
/// <summary>
/// Checks 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 filter matches, else false.</returns>
private static bool IsFilterMatch(StringInstruction cmd, Item item, IReadOnlyDictionary<string, PropertyInfo> props)
{
return IsPropertyFiltered(cmd, item, props);
}
/// <summary>
/// Checks 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</param>
/// <returns>True if filtered, else false.</returns>
private static bool IsPropertyFiltered(StringInstruction cmd, Item item, IReadOnlyDictionary<string, PropertyInfo> props)
{
if (!props.TryGetValue(cmd.PropertyName, out var pi))
return false;
if (!pi.CanRead)
return false;
return pi.IsValueEqual(item, cmd.PropertyValue) == cmd.Evaluator;
}
/// <summary>
/// Checks if the object is filtered by the provided <see cref="filters"/>.
/// </summary>
/// <param name="filters">Filters which must be satisfied.</param>
/// <param name="obj">Object to check.</param>
/// <returns>True if <see cref="obj"/> matches all filters.</returns>
public static bool IsFilterMatch(IEnumerable<StringInstruction> filters, object obj)
{
foreach (var cmd in filters)
{
if (!ReflectUtil.HasProperty(obj, cmd.PropertyName, out var pi))
return false;
try
{
if (pi.IsValueEqual(obj, cmd.PropertyValue) == cmd.Evaluator)
continue;
}
// User provided inputs can mismatch the type's required value format, and fail to be compared.
catch (Exception e)
{
Debug.WriteLine($"Unable to compare {cmd.PropertyName} to {cmd.PropertyValue}.");
Debug.WriteLine(e.Message);
}
return false;
}
return true;
}
return true;
}
}
}

View File

@ -1,52 +1,48 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
namespace NHSE.Core
namespace NHSE.Core;
public class ItemProcessor(BatchMutator<Item> mut) : BatchProcessor<Item>(mut)
{
public class ItemProcessor : BatchProcessor<Item>
protected override bool CanModify(Item item) => true;
protected override bool Finalize(Item item) => true;
/// <summary>
/// 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 static void ScreenStrings(IEnumerable<StringInstruction> il)
{
public ItemProcessor(BatchMutator<Item> mut) : base(mut)
foreach (var i in il.Where(i => !i.PropertyValue.All(char.IsDigit)))
{
}
string pv = i.PropertyValue;
if (pv.StartsWith('$') && pv.Contains(','))
i.SetRandomRange(pv);
protected override bool CanModify(Item item) => true;
protected override bool Finalize(Item item) => true;
/// <summary>
/// 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)
{
foreach (var i in il.Where(i => !i.PropertyValue.All(char.IsDigit)))
{
string pv = i.PropertyValue;
if (pv.StartsWith("$") && pv.Contains(","))
i.SetRandRange(pv);
SetInstructionScreenedValue(i);
}
}
/// <summary>
/// Initializes the <see cref="StringInstruction"/> 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="i">Instruction to initialize.</param>
private static void SetInstructionScreenedValue(StringInstruction i)
{
switch (i.PropertyName)
{
case nameof(Item.ItemId) or nameof(Item.ExtensionItemId): i.SetScreenedValue(GameInfo.Strings.itemlistdisplay); return;
}
}
protected override void Initialize(StringInstructionSet[] sets)
{
foreach (var set in sets)
{
ScreenStrings(set.Filters);
ScreenStrings(set.Instructions);
}
SetInstructionScreenedValue(i);
}
}
}
/// <summary>
/// Initializes the <see cref="StringInstruction"/> 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="i">Instruction to initialize.</param>
private static void SetInstructionScreenedValue(StringInstruction i)
{
switch (i.PropertyName)
{
case nameof(Item.ItemId) or nameof(Item.ExtensionItemId): i.SetScreenedValue(GameInfo.Strings.itemlistdisplay); return;
}
}
protected override void Initialize(ReadOnlySpan<StringInstructionSet> sets)
{
foreach (var set in sets)
{
ScreenStrings(set.Filters);
ScreenStrings(set.Instructions);
}
}
}

View File

@ -3,48 +3,82 @@
using System.Linq;
using System.Reflection;
namespace NHSE.Core
namespace NHSE.Core;
public class ItemReflection
{
public class ItemReflection
public static ItemReflection Default { get; } = new();
public readonly Type[] Types = [typeof(Item), typeof(VillagerItem)];
public 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()
{
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 ItemReflection()
{
Props = Types
.Select(z => ReflectUtil.GetAllPropertyInfoPublic(z)
.GroupBy(p => p.Name)
.Select(g => g.First())
.ToDictionary(p => p.Name))
.ToArray();
Properties = GetPropArray();
}
public string[][] GetPropArray()
{
var p = new string[Types.Length][];
for (int i = 0; i < p.Length; i++)
{
var pz = ReflectUtil.GetPropertiesPublic(Types[i]);
p[i] = pz.OrderBy(a => a).ToArray();
}
// 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;
}
Props = GetPropertyDictionaries(Types);
GetProperties = new Lazy<string[][]>(() => GetPropArray(Props, CustomProperties));
}
}
private static Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>>[] GetPropertyDictionaries(ReadOnlySpan<Type> types)
{
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 type = types[i].Dictionary;
string[] combine = [.. type.Keys, .. extra];
Array.Sort(combine);
p[i] = combine;
}
// Properties for any PKM
// Properties shared by all PKM
var first = p[0];
var any = new HashSet<string>(first);
var all = new HashSet<string>(first);
foreach (var set in p[1..])
{
any.UnionWith(set);
all.IntersectWith(set);
}
var arrAny = any.ToArray();
Array.Sort(arrAny);
result[0] = arrAny;
var arrAll = all.ToArray();
Array.Sort(arrAll);
result[^1] = arrAll;
return result;
}
}

View File

@ -1,28 +1,27 @@
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.
/// The data has invalid data and is not a suitable candidate for modification.
/// </summary>
public enum ModifyResult
{
/// <summary>
/// The data has invalid data and is not a suitable candidate for modification.
/// </summary>
Invalid,
Invalid,
/// <summary>
/// An error was occurred while iterating modifications for this data.
/// </summary>
Error,
/// <summary>
/// An error was occurred while iterating modifications for this data.
/// </summary>
Error,
/// <summary>
/// The data was skipped due to a matching Filter.
/// </summary>
Filtered,
/// <summary>
/// The data was skipped due to a matching Filter.
/// </summary>
Filtered,
/// <summary>
/// The data was modified.
/// </summary>
Modified,
}
}
/// <summary>
/// The data was modified.
/// </summary>
Modified,
}

View File

@ -1,101 +1,375 @@
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>
/// <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)
{
int index = arr.IndexOf(PropertyValue);
if ((uint)index >= arr.Length)
return false;
PropertyValue = index.ToString();
return true;
}
/// <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,
];
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>
/// <remarks>
/// Can be a filter (skip), or a modification instruction (modify)
/// Example:
/// =Species=1
/// The second = is the split.
/// </remarks>
/// <see cref="Exclude"/>
/// <see cref="Require"/>
/// <see cref="Apply"/>
public sealed class StringInstruction
public const char SplitInstruction = '=';
// 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);
/// <summary>
/// Checks if the input <see cref="str"/> is a valid "random range" specification.
/// </summary>
public static bool IsRandomRange(ReadOnlySpan<char> str)
{
public string PropertyName { get; }
public string PropertyValue { get; private set; }
public bool Evaluator { get; private init; }
// Need at least one character on either side of the splitter char.
int index = str.IndexOf(SplitRange);
return index > 0 && index < str.Length - 1;
}
public StringInstruction(string name, string value)
/// <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)
{
PropertyName = name;
PropertyValue = value;
PropertyValue = RandomMinimum.ToString();
Debug.WriteLine($"{PropertyName} randomization range Min/Max same?");
}
public void SetScreenedValue(string[] arr)
else
{
int index = Array.IndexOf(arr, PropertyValue);
PropertyValue = index > -1 ? index.ToString() : PropertyValue;
}
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 = ',';
/// <summary>
/// Character which divides a property and a value.
/// </summary>
/// <remarks>
/// Example:
/// =Species=1
/// The second = is the split.
/// </remarks>
public const char SplitInstruction = '=';
// Extra Functionality
private int RandomMinimum, RandomMaximum;
public bool Random { get; private set; }
public int RandomValue => RandUtil.Rand.Next(RandomMinimum, RandomMaximum + 1);
public void SetRandRange(string pv)
{
string str = pv.Substring(1);
var split = str.Split(SplitRange);
int.TryParse(split[0], out RandomMinimum);
int.TryParse(split[1], out RandomMaximum);
if (RandomMinimum == RandomMaximum)
{
PropertyValue = RandomMinimum.ToString();
Debug.WriteLine(PropertyName + " randomization range Min/Max same?");
}
else
{
Random = true;
}
}
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 };
}
public static IEnumerable<StringInstruction> GetInstructions(IEnumerable<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]);
}
/// <summary>
/// Weeds out invalid lines and only returns those with a valid first character.
/// </summary>
private static IEnumerable<string> GetRelevantStrings(IEnumerable<string> lines, params char[] pieces)
{
return lines.Where(line => !string.IsNullOrEmpty(line) && pieces.Any(z => z == line[0]));
Random = true;
}
}
/// <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());
/// <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 result = new List<StringInstruction>(lines.Length);
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(SpanLineEnumerator 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"/> filters from the input <see cref="lines"/>.
/// </summary>
public static List<StringInstruction> GetFilters(IReadOnlyList<string> lines)
{
var result = new List<StringInstruction>(lines.Count);
foreach (var line in lines)
{
if (TryParseFilter(line, out var entry))
result.Add(entry);
}
return result;
}
/// <summary>
/// Gets a list of <see cref="StringInstruction"/> filters from the input <see cref="lines"/>.
/// </summary>
public static List<StringInstruction> GetFilters(IEnumerable<string> lines)
{
var result = new List<StringInstruction>();
foreach (var line in lines)
{
if (TryParseFilter(line, out var entry))
result.Add(entry);
}
return result;
}
/// <summary>
/// Gets a list of <see cref="StringInstruction"/> instructions from the input <see cref="text"/>.
/// </summary>
public static List<StringInstruction> GetInstructions(ReadOnlySpan<char> text) => GetInstructions(text.EnumerateLines());
/// <summary>
/// Gets a list of <see cref="StringInstruction"/> instructions from the input <see cref="lines"/>.
/// </summary>
public static List<StringInstruction> GetInstructions(ReadOnlySpan<string> lines)
{
var result = new List<StringInstruction>(lines.Length);
foreach (var line in lines)
{
if (TryParseInstruction(line, out var entry))
result.Add(entry);
}
return result;
}
/// <summary>
/// Gets a list of <see cref="StringInstruction"/> instructions from the input <see cref="lines"/>.
/// </summary>
public static List<StringInstruction> GetInstructions(SpanLineEnumerator lines)
{
var result = new List<StringInstruction>();
foreach (var line in lines)
{
if (TryParseInstruction(line, out var entry))
result.Add(entry);
}
return result;
}
/// <summary>
/// Gets a list of <see cref="StringInstruction"/> instructions from the input <see cref="lines"/>.
/// </summary>
public static List<StringInstruction> GetInstructions(IReadOnlyList<string> lines)
{
var result = new List<StringInstruction>(lines.Count);
foreach (var line in lines)
{
if (TryParseInstruction(line, out var entry))
result.Add(entry);
}
return result;
}
/// <summary>
/// Gets a list of <see cref="StringInstruction"/> instructions from the input <see cref="lines"/>.
/// </summary>
public static List<StringInstruction> GetInstructions(IEnumerable<string> lines)
{
var result = new List<StringInstruction>();
foreach (var line in lines)
{
if (TryParseInstruction(line, out var entry))
result.Add(entry);
}
return result;
}
/// <summary>
/// Tries to parse a <see cref="StringInstruction"/> filter from the input <see cref="line"/>.
/// </summary>
public static bool TryParseFilter(ReadOnlySpan<char> line, [NotNullWhen(true)] out StringInstruction? entry)
{
entry = null;
if (line.Length is 0)
return false;
var comparer = GetComparer(line[0]);
if (!comparer.IsSupported)
return false;
return TryParseSplitTuple(line[1..], ref entry, comparer);
}
/// <summary>
/// Tries to parse a <see cref="StringInstruction"/> instruction from the input <see cref="line"/>.
/// </summary>
public static bool TryParseInstruction(ReadOnlySpan<char> line, [NotNullWhen(true)] out StringInstruction? entry)
{
entry = null;
if (!line.StartsWith(Apply))
return false;
return TryParseSplitTuple(line[1..], ref entry);
}
/// <summary>
/// Tries to split a <see cref="StringInstruction"/> tuple from the input <see cref="tuple"/>.
/// </summary>
public static bool TryParseSplitTuple(ReadOnlySpan<char> tuple, [NotNullWhen(true)] ref StringInstruction? entry, InstructionComparer eval = default)
{
if (!TryParseSplitTuple(tuple, out var name, out var value))
return false;
entry = new StringInstruction(name.ToString(), value.ToString(), eval);
return true;
}
/// <summary>
/// Tries to split a <see cref="StringInstruction"/> tuple from the input <see cref="tuple"/>.
/// </summary>
public static bool TryParseSplitTuple(ReadOnlySpan<char> tuple, out ReadOnlySpan<char> name, out ReadOnlySpan<char> value)
{
name = default;
value = default;
var splitIndex = tuple.IndexOf(SplitInstruction);
if (splitIndex <= 0)
return false;
name = tuple[..splitIndex];
if (name.IsWhiteSpace())
return false;
value = tuple[(splitIndex + 1)..];
var noExtra = value.IndexOf(SplitInstruction);
return noExtra == -1;
}
/// <summary>
/// Gets the <see cref="InstructionComparer"/> from the input <see cref="opCode"/>.
/// </summary>
public static InstructionComparer GetComparer(char opCode) => opCode switch
{
FilterEqual => IsEqual,
FilterNotEqual => IsNotEqual,
FilterGreaterThan => IsGreaterThan,
FilterLessThan => IsLessThan,
FilterGreaterThanOrEqual => IsGreaterThanOrEqual,
FilterLessThanOrEqual => IsLessThanOrEqual,
_ => None,
};
}
/// <summary>
/// Value comparison type
/// </summary>
public enum InstructionComparer : byte
{
None,
IsEqual,
IsNotEqual,
IsGreaterThan,
IsGreaterThanOrEqual,
IsLessThan,
IsLessThanOrEqual,
}
/// <summary>
/// Extension methods for <see cref="InstructionComparer"/>
/// </summary>
public static class InstructionComparerExtensions
{
extension(InstructionComparer comparer)
{
/// <summary>
/// Indicates if the <see cref="comparer"/> is supported by the logic.
/// </summary>
/// <returns>True if supported, false if unsupported.</returns>
public bool IsSupported => comparer switch
{
IsEqual => true,
IsNotEqual => true,
IsGreaterThan => true,
IsGreaterThanOrEqual => true,
IsLessThan => true,
IsLessThanOrEqual => true,
_ => false,
};
/// <summary>
/// Checks if the compare operator is satisfied by a boolean comparison result.
/// </summary>
/// <param name="compareResult">Result from Equals comparison</param>
/// <returns>True if satisfied</returns>
/// <remarks>Only use this method if the comparison is boolean only. Use the <see cref="IsCompareOperator"/> otherwise.</remarks>
public bool IsCompareEquivalence(bool compareResult) => comparer switch
{
IsEqual => compareResult,
IsNotEqual => !compareResult,
_ => false,
};
/// <summary>
/// Checks if the compare operator is satisfied by the <see cref="IComparable.CompareTo"/> result.
/// </summary>
/// <param name="compareResult">Result from CompareTo</param>
/// <returns>True if satisfied</returns>
public bool IsCompareOperator(int compareResult) => comparer switch
{
IsEqual => compareResult is 0,
IsNotEqual => compareResult is not 0,
IsGreaterThan => compareResult > 0,
IsGreaterThanOrEqual => compareResult >= 0,
IsLessThan => compareResult < 0,
IsLessThanOrEqual => compareResult <= 0,
_ => false,
};
}
}

View File

@ -1,38 +1,143 @@
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 char SetSeparatorChar = ';';
public StringInstructionSet(IReadOnlyList<StringInstruction> filters, IReadOnlyList<StringInstruction> instructions)
{
public readonly IReadOnlyList<StringInstruction> Filters;
public readonly IReadOnlyList<StringInstruction> Instructions;
private const string SetSeparator = ";";
public StringInstructionSet(IReadOnlyList<StringInstruction> filters, IReadOnlyList<StringInstruction> instructions)
{
Filters = filters;
Instructions = instructions;
}
public StringInstructionSet(ICollection<string> set)
{
Filters = StringInstruction.GetFilters(set).ToList();
Instructions = StringInstruction.GetInstructions(set).ToList();
}
public static IEnumerable<StringInstructionSet> GetBatchSets(IList<string> lines)
{
int start = 0;
while (start < lines.Count)
{
var list = lines.Skip(start).TakeWhile(_ => !lines[start++].StartsWith(SetSeparator)).ToList();
yield return new StringInstructionSet(list);
}
}
Filters = filters;
Instructions = instructions;
}
}
public StringInstructionSet(ReadOnlySpan<char> text)
{
var set = text.EnumerateLines();
Filters = StringInstruction.GetFilters(set);
Instructions = StringInstruction.GetInstructions(set);
}
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 < text.Length)
{
var line = text[start..];
if (line.Length != 0 && line[0] == SetSeparatorChar)
return start;
var next = line.IndexOf('\n');
if (next == -1)
return text.Length;
start += next + 1;
}
return start;
}
/// <summary>
/// Scans through the <see cref="lines"/> to count the amount of valid lines to consume.
/// </summary>
/// <returns>Amount of lines comprising a set of instructions.</returns>
public static int GetInstructionSetLength(ReadOnlySpan<string> lines)
{
int start = 0;
while (start < lines.Length)
{
var line = lines[start++];
if (line.StartsWith(SetSeparatorChar))
return start;
}
return start;
}
}

View File

@ -1,24 +1,23 @@
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;
/// <summary> Y Coordinate within the Field Item Layer </summary>
public readonly int Y;
/// <summary> Offset relative to the start of the Field Item Layer </summary>
public readonly int Offset;
public readonly byte[] Data;
public FieldItemColumn(int x, int y, int offset, byte[] data)
{
/// <summary> X Coordinate within the Field Item Layer </summary>
public readonly int X;
/// <summary> Y Coordinate within the Field Item Layer </summary>
public readonly int Y;
/// <summary> Offset relative to the start of the Field Item Layer </summary>
public readonly int Offset;
public readonly byte[] Data;
public FieldItemColumn(int x, int y, int offset, byte[] data)
{
X = x;
Y = y;
Offset = offset;
Data = data;
}
X = x;
Y = y;
Offset = offset;
Data = data;
}
}
}

View File

@ -1,126 +1,126 @@
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;
// Each dropped item is a 2x2 square, with the top left tile being the root node, and the other 3 being extensions pointing back to the root.
/// <inheritdoc cref="CanFitDropped(int,int,int,int,int,int,int,int)"/>
/// <param name="x">Raw X coordinate for the top-left item root tile.</param>
/// <param name="y">Raw Y coordinate for the top-left item root tile.</param>
/// <param name="totalCount">Total count of items to be dropped (not tiles).</param>
/// <param name="yCount">Count of items tall the overall spawn-rectangle is.</param>
/// <param name="borderX">Excluded outer tile count. Useful for enforcing that beach acre tiles are skipped.</param>
/// <param name="borderY">Excluded outer tile count. Useful for enforcing that beach acre tiles are skipped.</param>
public static bool CanFitDropped(int x, int y, int totalCount, int yCount, int borderX, int borderY)
{
private const int MapHeight = FieldItemLayer.FieldItemHeight;
private const int MapWidth = FieldItemLayer.FieldItemWidth;
// Each dropped item is a 2x2 square, with the top left tile being the root node, and the other 3 being extensions pointing back to the root.
/// <inheritdoc cref="CanFitDropped(int,int,int,int,int,int,int,int)"/>
/// <param name="x">Raw X coordinate for the top-left item root tile.</param>
/// <param name="y">Raw Y coordinate for the top-left item root tile.</param>
/// <param name="totalCount">Total count of items to be dropped (not tiles).</param>
/// <param name="yCount">Count of items tall the overall spawn-rectangle is.</param>
/// <param name="borderX">Excluded outer tile count. Useful for enforcing that beach acre tiles are skipped.</param>
/// <param name="borderY">Excluded outer tile count. Useful for enforcing that beach acre tiles are skipped.</param>
public static bool CanFitDropped(int x, int y, int totalCount, int yCount, int borderX, int borderY)
{
return CanFitDropped(x, y, totalCount, yCount, borderX, borderX, borderY, borderY);
}
/// <summary>
/// Checks if the requested <see cref="totalCount"/> of items can be dropped on the field item layer. Does not check terrain or existing items.
/// </summary>
/// <remarks>Coordinates should be 32x32 style instead of 16x16.</remarks>
/// <param name="x">Raw X coordinate for the top-left item root tile.</param>
/// <param name="y">Raw Y coordinate for the top-left item root tile.</param>
/// <param name="totalCount">Total count of items to be dropped (not tiles).</param>
/// <param name="yCount">Count of items tall the overall spawn-rectangle is.</param>
/// <param name="leftX">Excluded outer tile count. Useful for enforcing that beach acre tiles are skipped.</param>
/// <param name="rightX">Excluded outer tile count. Useful for enforcing that beach acre tiles are skipped.</param>
/// <param name="topY">Excluded outer tile count. Useful for enforcing that beach acre tiles are skipped.</param>
/// <param name="botY">Excluded outer tile count. Useful for enforcing that beach acre tiles are skipped.</param>
/// <returns>True if can fit, false if not.</returns>
public static bool CanFitDropped(int x, int y, int totalCount, int yCount, int leftX, int rightX, int topY, int botY)
{
var xCount = totalCount / yCount;
if (x < leftX || (x + (xCount * 2)) > MapWidth - rightX)
return false;
if (y < topY || (y + (yCount * 2)) > MapHeight - botY)
return false;
return totalCount < (MapHeight * MapWidth / 32);
}
public static IReadOnlyList<FieldItemColumn> InjectItemsAsDropped(int mapX, int mapY, IReadOnlyList<Item> item)
{
int yStride = (item.Count > 16) ? 16 : item.Count;
return InjectItemsAsDropped(mapX, mapY, item, yStride);
}
public static IReadOnlyList<FieldItemColumn> InjectItemsAsDropped(int mapX, int mapY, IReadOnlyList<Item> item, int yStride)
{
var xStride = item.Count / yStride;
List<FieldItemColumn> result = new(yStride * xStride);
for (int i = 0; i < xStride; i++)
{
var x = mapX + (i * 2);
var y = mapY;
var itemSlice = item.Skip(i * yStride).Take(yStride).ToArray();
// Root+ExtensionY
var offset = GetTileOffset(x, y);
var data = GetColumnRoot(itemSlice);
var column = new FieldItemColumn(x, y, offset, data);
result.Add(column);
// Ex X/XY
++x;
offset = GetTileOffset(x, y);
data = GetColumnExtension(itemSlice);
column = new FieldItemColumn(x, y, offset, data);
result.Add(column);
}
return result;
}
private static byte[] GetColumnRoot(Item[] items)
{
var col = new Item[items.Length * 2];
for (int i = 0; i < items.Length; i++)
{
var item = items[i];
var idx = i * 2;
col[idx] = GetDroppedItem(item);
col[idx + 1] = GetExtension(item, 0, 1);
}
return Item.SetArray(col);
}
private static byte[] GetColumnExtension(Item[] items)
{
var col = new Item[items.Length * 2];
for (int i = 0; i < items.Length; i++)
{
var item = items[i];
var idx = i * 2;
col[idx] = GetExtension(item, 1, 0);
col[idx + 1] = GetExtension(item, 1, 1);
}
return Item.SetArray(col);
}
private static int GetTileOffset(int x, int y)
{
return Item.SIZE * (y + (x * MapHeight));
}
private static Item GetDroppedItem(Item item)
{
var copy = new Item();
copy.CopyFrom(item);
copy.ClearFlags();
copy.IsDropped = true;
return copy;
}
private static Item GetExtension(Item item, byte x, byte y) => new(Item.EXTENSION) { ExtensionItemId = item.ItemId, ExtensionX = x, ExtensionY = y };
return CanFitDropped(x, y, totalCount, yCount, borderX, borderX, borderY, borderY);
}
}
/// <summary>
/// Checks if the requested <see cref="totalCount"/> of items can be dropped on the field item layer. Does not check terrain or existing items.
/// </summary>
/// <remarks>Coordinates should be 32x32 style instead of 16x16.</remarks>
/// <param name="x">Raw X coordinate for the top-left item root tile.</param>
/// <param name="y">Raw Y coordinate for the top-left item root tile.</param>
/// <param name="totalCount">Total count of items to be dropped (not tiles).</param>
/// <param name="yCount">Count of items tall the overall spawn-rectangle is.</param>
/// <param name="leftX">Excluded outer tile count. Useful for enforcing that beach acre tiles are skipped.</param>
/// <param name="rightX">Excluded outer tile count. Useful for enforcing that beach acre tiles are skipped.</param>
/// <param name="topY">Excluded outer tile count. Useful for enforcing that beach acre tiles are skipped.</param>
/// <param name="botY">Excluded outer tile count. Useful for enforcing that beach acre tiles are skipped.</param>
/// <returns>True if can fit, false if not.</returns>
public static bool CanFitDropped(int x, int y, int totalCount, int yCount, int leftX, int rightX, int topY, int botY)
{
var xCount = totalCount / yCount;
if (x < leftX || (x + (xCount * 2)) > MapWidth - rightX)
return false;
if (y < topY || (y + (yCount * 2)) > MapHeight - botY)
return false;
return totalCount < (MapHeight * MapWidth / 32);
}
public static IReadOnlyList<FieldItemColumn> InjectItemsAsDropped(int mapX, int mapY, IReadOnlyList<Item> item)
{
int yStride = (item.Count > 16) ? 16 : item.Count;
return InjectItemsAsDropped(mapX, mapY, item, yStride);
}
public static IReadOnlyList<FieldItemColumn> InjectItemsAsDropped(int mapX, int mapY, IReadOnlyList<Item> item, int yStride)
{
var xStride = item.Count / yStride;
List<FieldItemColumn> result = new(yStride * xStride);
for (int i = 0; i < xStride; i++)
{
var x = mapX + (i * 2);
var y = mapY;
var itemSlice = item.Skip(i * yStride).Take(yStride).ToArray();
// Root+ExtensionY
var offset = GetTileOffset(x, y);
var data = GetColumnRoot(itemSlice);
var column = new FieldItemColumn(x, y, offset, data);
result.Add(column);
// Ex X/XY
++x;
offset = GetTileOffset(x, y);
data = GetColumnExtension(itemSlice);
column = new FieldItemColumn(x, y, offset, data);
result.Add(column);
}
return result;
}
private static byte[] GetColumnRoot(ReadOnlySpan<Item> items)
{
var col = new Item[items.Length * 2];
for (int i = 0; i < items.Length; i++)
{
var item = items[i];
var idx = i * 2;
col[idx] = GetDroppedItem(item);
col[idx + 1] = GetExtension(item, 0, 1);
}
return Item.SetArray(col);
}
private static byte[] GetColumnExtension(ReadOnlySpan<Item> items)
{
var col = new Item[items.Length * 2];
for (int i = 0; i < items.Length; i++)
{
var item = items[i];
var idx = i * 2;
col[idx] = GetExtension(item, 1, 0);
col[idx + 1] = GetExtension(item, 1, 1);
}
return Item.SetArray(col);
}
private static int GetTileOffset(int x, int y)
{
return Item.SIZE * (y + (x * MapHeight));
}
private static Item GetDroppedItem(Item item)
{
var copy = new Item();
copy.CopyFrom(item);
copy.ClearFlags();
copy.IsDropped = true;
return copy;
}
private static Item GetExtension(Item item, byte x, byte y) => new(Item.EXTENSION) { ExtensionItemId = item.ItemId, ExtensionX = x, ExtensionY = y };
}

View File

@ -1,135 +1,135 @@
using System;
using System.Collections.Generic;
using System.Linq;
using static System.Buffers.Binary.BinaryPrimitives;
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Handles operations for parsing the player inventory.
/// </summary>
/// <remarks>
/// Player Inventory is comprised of multiple values, which we won't bother reimplementing as a convertible data structure.
/// <br>Refer to GSavePlayerItemBaggage's ItemBag &amp; ItemPocket in the dumped c-structure schema.</br>
/// </remarks>
public static class PlayerItemSet
{
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;
private const int ItemSet_MetaSize = 4 + ItemSet_ItemCount;
private const int ItemSet_TotalSize = (ItemSet_ItemSize + ItemSet_MetaSize) * ItemSet_Quantity;
private const int ShiftToTopOfStructure = -ItemSet_MetaSize - (Item.SIZE * ItemSet_ItemCount); // shifts slot1 offset => top of data structure
/// <summary>
/// Handles operations for parsing the player inventory.
/// Gets the Offset and Size to read from based on the Item 1 RAM offset.
/// </summary>
/// <remarks>
/// Player Inventory is comprised of multiple values, which we won't bother reimplementing as a convertible data structure.
/// <br>Refer to GSavePlayerItemBaggage's ItemBag &amp; ItemPocket in the dumped c-structure schema.</br>
/// </remarks>
public static class PlayerItemSet
/// <param name="slot1">Item Slot 1 offset in RAM</param>
/// <param name="offset">Offset to read from</param>
/// <param name="length">Length of data to read from <see cref="offset"/></param>
public static void GetOffsetLength(uint slot1, out uint offset, out int length)
{
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;
private const int ItemSet_MetaSize = 4 + ItemSet_ItemCount;
private const int ItemSet_TotalSize = (ItemSet_ItemSize + ItemSet_MetaSize) * ItemSet_Quantity;
private const int ShiftToTopOfStructure = -ItemSet_MetaSize - (Item.SIZE * ItemSet_ItemCount); // shifts slot1 offset => top of data structure
/// <summary>
/// Gets the Offset and Size to read from based on the Item 1 RAM offset.
/// </summary>
/// <param name="slot1">Item Slot 1 offset in RAM</param>
/// <param name="offset">Offset to read from</param>
/// <param name="length">Length of data to read from <see cref="offset"/></param>
public static void GetOffsetLength(uint slot1, out uint offset, out int length)
{
offset = (uint)((int)slot1 + ShiftToTopOfStructure);
length = ItemSet_TotalSize;
}
/// <summary>
/// Compares the raw data to the expected data layout.
/// </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)
{
// Check the unlocked slot count -- expect 0,10,20
var bagCount = BitConverter.ToUInt32(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);
if (pocketCount != ItemSet_ItemCount) // pouch0-19 count should be 20.
return false;
// Check the item wheel binding -- expect -1 or [0,7]
// Disallow duplicate binds!
// Don't bother checking that bind[i] (when ! -1) is not NONE at items[i]. We don't need to check everything!
var bound = new List<byte>();
if (!ValidateBindList(data, ItemSet_ItemSize + 4, bound))
return false;
if (!ValidateBindList(data, ItemSet_ItemSize + 4 + (ItemSet_ItemSize + ItemSet_MetaSize), bound))
return false;
return true;
}
private static bool ValidateBindList(byte[] data, int bindStart, ICollection<byte> bound)
{
for (int i = 0; i < ItemSet_ItemCount; i++)
{
var bind = data[bindStart + i];
if (bind == 0xFF) // Not bound
continue;
if (bind > 7) // Only [0,7] permitted as the wheel has 8 spots
return false;
if (bound.Contains(bind)) // Wheel index is already bound to another item slot
return false;
bound.Add(bind);
}
return true;
}
/// <summary>
/// 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)
{
var items = GetEmptyItemArray(40);
ReadPlayerInventory(data, items);
return items;
}
private static Item[] GetEmptyItemArray(int count)
{
var items = new Item[count];
for (int i = 0; i < items.Length; i++)
items[i] = new Item();
return items;
}
/// <summary>
/// Reads the items present in the player inventory packet into the <see cref="destination"/> list of items.
/// </summary>
/// <param name="data">Player Inventory packet</param>
/// <param name="destination">40 Item array</param>
public static void ReadPlayerInventory(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 p2 = Item.GetArray(data.Slice(ItemSet_ItemSize + 0x18, ItemSet_ItemSize));
for (int i = 0; i < pocket1.Length; i++)
pocket1[i].CopyFrom(p1[i]);
for (int i = 0; i < pocket2.Length; i++)
pocket2[i].CopyFrom(p2[i]);
}
/// <summary>
/// Writes the items in the <see cref="source"/> list of items to the player inventory packet.
/// </summary>
/// <param name="data">Player Inventory packet</param>
/// <param name="source">40 Item array</param>
public static void WritePlayerInventory(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);
}
offset = (uint)((int)slot1 + ShiftToTopOfStructure);
length = ItemSet_TotalSize;
}
}
/// <summary>
/// Compares the raw data to the expected data layout.
/// </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(ReadOnlySpan<byte> data)
{
// Check the unlocked slot count -- expect 0,10,20
var bagCount = ReadUInt32LittleEndian(data[ItemSet_ItemSize..]);
if (bagCount > ItemSet_ItemCount || bagCount % 10 != 0) // pouch21-39 count
return false;
var pocketCount = ReadUInt32LittleEndian(data[(ItemSet_ItemSize + ItemSet_MetaSize + ItemSet_ItemSize)..]);
if (pocketCount != ItemSet_ItemCount) // pouch0-19 count should be 20.
return false;
// Check the item wheel binding -- expect -1 or [0,7]
// Disallow duplicate binds!
// Don't bother checking that bind[i] (when ! -1) is not NONE at items[i]. We don't need to check everything!
var bound = new List<byte>();
if (!ValidateBindList(data, ItemSet_ItemSize + 4, bound))
return false;
if (!ValidateBindList(data, ItemSet_ItemSize + 4 + (ItemSet_ItemSize + ItemSet_MetaSize), bound))
return false;
return true;
}
private static bool ValidateBindList(ReadOnlySpan<byte> data, int bindStart, ICollection<byte> bound)
{
for (int i = 0; i < ItemSet_ItemCount; i++)
{
var bind = data[bindStart + i];
if (bind == 0xFF) // Not bound
continue;
if (bind > 7) // Only [0,7] permitted as the wheel has 8 spots
return false;
if (bound.Contains(bind)) // Wheel index is already bound to another item slot
return false;
bound.Add(bind);
}
return true;
}
/// <summary>
/// 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(ReadOnlySpan<byte> data)
{
var items = GetEmptyItemArray(40);
ReadPlayerInventory(data, items);
return items;
}
private static Item[] GetEmptyItemArray(int count)
{
var items = new Item[count];
for (int i = 0; i < items.Length; i++)
items[i] = new Item();
return items;
}
/// <summary>
/// Reads the items present in the player inventory packet into the <see cref="destination"/> list of items.
/// </summary>
/// <param name="data">Player Inventory packet</param>
/// <param name="destination">40 Item array</param>
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[..ItemSet_ItemSize]);
var p2 = Item.GetArray(data.Slice(ItemSet_ItemSize + 0x18, ItemSet_ItemSize));
for (int i = 0; i < pocket1.Length; i++)
pocket1[i].CopyFrom(p1[i]);
for (int i = 0; i < pocket2.Length; i++)
pocket2[i].CopyFrom(p2[i]);
}
/// <summary>
/// Writes the items in the <see cref="source"/> list of items to the player inventory packet.
/// </summary>
/// <param name="data">Player Inventory packet</param>
/// <param name="source">40 Item array</param>
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);
p2.CopyTo(data[(ItemSet_ItemSize + 0x18)..]);
}
}

View File

@ -1,23 +1,22 @@
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.
/// Checks if the item should have wrapping paper applied.
/// </summary>
public interface IConfigItem
{
/// <summary>
/// Checks if the item should have wrapping paper applied.
/// </summary>
bool WrapAllItems { get; }
bool WrapAllItems { get; }
/// <summary>
/// Wrapping paper type applied if <see cref="WrapAllItems"/> is set.
/// </summary>
ItemWrappingPaper WrappingPaper { get; }
/// <summary>
/// Wrapping paper type applied if <see cref="WrapAllItems"/> is set.
/// </summary>
ItemWrappingPaper WrappingPaper { get; }
/// <summary>
/// Checks if the Drop Compatibility check should be skipped.
/// </summary>
bool SkipDropCheck { get; }
}
}
/// <summary>
/// Checks if the Drop Compatibility check should be skipped.
/// </summary>
bool SkipDropCheck { get; }
}

View File

@ -2,383 +2,385 @@
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.
/// Invert the recipe dictionary so we can look up recipe IDs from an input item ID.
/// </summary>
public static class ItemParser
public static readonly IReadOnlyDictionary<ushort, ushort> InvertedRecipeDictionary =
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"];
/// <summary>
/// Gets a list of items from the requested hex string(s).
/// </summary>
/// <remarks>
/// If the first input is a language code (2 characters), the logic will try to parse item names for that language instead of item IDs.
/// </remarks>
/// <param name="requestHex">8 byte hex item values (u64 format)</param>
/// <param name="cfg">Options for packaging items</param>
/// <param name="type">End destination of the item</param>
public static IReadOnlyCollection<Item> GetItemsFromUserInput(string requestHex, IConfigItem cfg, ItemDestination type = ItemDestination.PlayerDropped)
{
/// <summary>
/// Invert the recipe dictionary so we can look up recipe IDs from an input item ID.
/// </summary>
public static readonly IReadOnlyDictionary<ushort, ushort> InvertedRecipeDictionary =
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"};
/// <summary>
/// Gets a list of items from the requested hex string(s).
/// </summary>
/// <remarks>
/// If the first input is a language code (2 characters), the logic will try to parse item names for that language instead of item IDs.
/// </remarks>
/// <param name="requestHex">8 byte hex item values (u64 format)</param>
/// <param name="cfg">Options for packaging items</param>
/// <param name="type">End destination of the item</param>
public static IReadOnlyCollection<Item> GetItemsFromUserInput(string requestHex, IConfigItem cfg, ItemDestination type = ItemDestination.PlayerDropped)
try
{
try
{
// having a language 2char code will cause an exception in parsing; this is fine and is handled by our catch statement.
var split = requestHex.Split(SplittersHex, StringSplitOptions.RemoveEmptyEntries);
return GetItemsHexCode(split, cfg, type);
}
catch
{
var split = requestHex.Split(SplittersName, StringSplitOptions.RemoveEmptyEntries);
return GetItemsLanguage(split, cfg, type, GameLanguage.DefaultLanguage);
}
// having a language 2char code will cause an exception in parsing; this is fine and is handled by our catch statement.
var split = requestHex.Split(SplittersHex, StringSplitOptions.RemoveEmptyEntries);
return GetItemsHexCode(split, cfg, type);
}
/// <summary>
/// Gets a list of DIY item cards from the requested list of DIY IDs.
/// </summary>
/// <remarks>
/// If the first input is a language code (2 characters), the logic will try to parse item names for that language instead of DIY IDs.
/// </remarks>
/// <param name="requestHex">8 byte hex item values (u64 format)</param>
public static IReadOnlyCollection<Item> GetDIYsFromUserInput(string requestHex)
catch
{
try
{
// having a language 2char code will cause an exception in parsing; this is fine and is handled by our catch statement.
var split = requestHex.Split(SplittersHex, StringSplitOptions.RemoveEmptyEntries);
return GetDIYItemsHexCode(split);
}
catch
{
var split = requestHex.Split(SplittersName, StringSplitOptions.RemoveEmptyEntries);
return GetDIYItemsLanguage(split);
}
}
/// <summary>
/// Gets a list of items from the requested list of DIY hex code strings.
/// </summary>
/// <remarks>
/// If a hex code parse fails or a recipe ID does not exist, exceptions will be thrown.
/// </remarks>
/// <param name="split">List of recipe IDs as u16 hex</param>
public static IReadOnlyCollection<Item> GetDIYItemsHexCode(IReadOnlyList<string> split)
{
var result = new Item[split.Count];
for (int i = 0; i < result.Length; i++)
{
var text = split[i].Trim();
bool parse = ulong.TryParse(text, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out var value);
if (!parse)
throw new Exception($"Item value out of expected range ({text}).");
if (!RecipeList.Recipes.TryGetValue((ushort)value, out _))
throw new Exception($"DIY recipe appears to be invalid ({text}).");
result[i] = new Item(Item.DIYRecipe) { Count = (ushort)value };
}
return result;
}
/// <summary>
/// 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.
/// </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>
public static IReadOnlyCollection<Item> GetDIYItemsLanguage(IReadOnlyList<string> split, string lang = GameLanguage.DefaultLanguage)
{
if (split.Count > 1 && split[0].Length < 3)
{
var langIndex = GameLanguage.GetLanguageIndex(split[0]);
lang = GameLanguage.Language2Char(langIndex);
split = split.Skip(1).ToArray();
}
var result = new Item[split.Count];
for (int i = 0; i < result.Length; i++)
{
var text = split[i].Trim();
var item = GetItem(text, lang);
if (!InvertedRecipeDictionary.TryGetValue(item.ItemId, out var diy))
throw new Exception($"DIY recipe appears to be invalid ({text}).");
result[i] = new Item(Item.DIYRecipe) { Count = diy };
}
return result;
}
/// <summary>
/// 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.
/// </remarks>
/// <param name="split">List of item names</param>
/// <param name="config">Item packaging options</param>
/// <param name="type">Destination where the item will end up at</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>
public static IReadOnlyCollection<Item> GetItemsLanguage(IReadOnlyList<string> split, IConfigItem config, ItemDestination type = ItemDestination.PlayerDropped, string lang = GameLanguage.DefaultLanguage)
{
if (split.Count > 1 && split[0].Length < 3)
{
var langIndex = GameLanguage.GetLanguageIndex(split[0]);
lang = GameLanguage.Language2Char(langIndex);
split = split.Skip(1).ToArray();
}
var strings = GameInfo.Strings.itemlistdisplay;
var result = new Item[split.Count];
for (int i = 0; i < result.Length; i++)
{
var text = split[i].Trim();
var item = CreateItem(text, i, config, type, lang);
if (item.ItemId >= strings.Length)
throw new Exception($"Item requested is out of expected range ({item.ItemId:X4} > {strings.Length:X4}).");
if (string.IsNullOrWhiteSpace(strings[item.ItemId]))
throw new Exception($"Item requested does not have a valid name ({item.ItemId:X4}).");
result[i] = item;
}
return result;
}
/// <summary>
/// Gets a list of items from the requested list of item hex code strings.
/// </summary>
/// <remarks>
/// If a hex code parse fails or a recipe ID does not exist, exceptions will be thrown.
/// </remarks>
/// <param name="split">List of recipe IDs as u16 hex</param>
/// <param name="config">Item packaging options</param>
/// <param name="type">Destination where the item will end up at</param>
public static IReadOnlyCollection<Item> GetItemsHexCode(IReadOnlyList<string> split, IConfigItem config, ItemDestination type = ItemDestination.PlayerDropped)
{
var strings = GameInfo.Strings.itemlistdisplay;
var result = new Item[split.Count];
for (int i = 0; i < result.Length; i++)
{
var text = split[i].Trim();
var convert = GetBytesFromString(text);
var item = CreateItem(convert, i, config, type);
if (item.ItemId >= strings.Length)
throw new Exception($"Item requested is out of expected range ({item.ItemId:X4} > {strings.Length:X4}).");
if (string.IsNullOrWhiteSpace(strings[item.ItemId]))
throw new Exception($"Item requested does not have a valid name ({item.ItemId:X4}).");
result[i] = item;
}
return result;
}
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);
}
private static Item CreateItem(string name, int requestIndex, IConfigItem config, ItemDestination type, string lang = "en")
{
var item = GetItem(name, lang);
if (item.IsNone)
throw new Exception($"Failed to convert item (index {requestIndex}: {name}) for Language {lang}.");
return FinalizeItem(requestIndex, config, type, item);
}
private static Item CreateItem(byte[] convert, int requestIndex, IConfigItem config, ItemDestination type)
{
Item item;
try
{
if (convert.Length != Item.SIZE)
throw new Exception();
item = convert.ToClass<Item>();
}
catch (Exception ex)
{
throw new Exception($"Failed to convert item (index {requestIndex}: {ex.Message}).");
}
return FinalizeItem(requestIndex, config, type, item);
}
private static Item FinalizeItem(int requestIndex, IConfigItem config, ItemDestination type, Item item)
{
if (type == ItemDestination.PlayerDropped)
{
if (!ItemInfo.IsSaneItemForDrop(item) && !config.SkipDropCheck)
throw new Exception($"Unsupported item: (index {requestIndex}).");
if (config.WrapAllItems && item.ShouldWrapItem())
item.SetWrapping(ItemWrapping.WrappingPaper, config.WrappingPaper, true);
}
item.IsDropped = type == ItemDestination.FieldItemDropped;
return item;
}
private static readonly CompareInfo Comparer = CultureInfo.InvariantCulture.CompareInfo;
private const CompareOptions optIncludeSymbols = CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreWidth;
private const CompareOptions optIgnoreSymbols = CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreWidth;
/// <summary>
/// Gets a sensitive compare option, depending on the input string's qualities.
/// </summary>
/// <param name="str">Input string</param>
/// <returns>Default options if no symbols,</returns>
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).
/// </summary>
/// <param name="itemName">Requested Item</param>
/// <param name="lang">Game strings language to fetch with</param>
/// <returns>Returns <see cref="Item.NO_ITEM"/> if no match found.</returns>
public static Item GetItem(string itemName, string lang = "en")
{
var gStrings = GameInfo.GetStrings(lang);
var strings = gStrings.ItemDataSource;
var parsedItem = GetItem(itemName, strings);
if (parsedItem != Item.NO_ITEM)
return parsedItem;
if (gStrings.HasAssociatedItems(itemName, out var items))
{
if (items?.Count == 1)
return new Item((ushort)items[0].Value);
}
return Item.NO_ITEM;
}
/// <summary>
/// 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>
/// <returns>Returns <see cref="Item.NO_ITEM"/> if no match found.</returns>
public static Item GetItem(string itemName, IReadOnlyList<ComboItem> strings)
{
if (TryGetItem(itemName, strings, out var id))
return new Item(id);
return Item.NO_ITEM;
}
/// <summary>
/// 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>
/// <param name="value">Item ID, if found. Otherwise, 0</param>
/// <returns>True if found, false if none.</returns>
public static bool TryGetItem(string itemName, IReadOnlyList<ComboItem> strings, out ushort value)
{
if (TryGetItem(itemName, strings, out value, optIncludeSymbols))
return true;
return TryGetItem(itemName, strings, out value, optIgnoreSymbols);
}
private static bool TryGetItem(string itemName, IEnumerable<ComboItem> strings, out ushort value, CompareOptions opt)
{
foreach (var item in strings)
{
var result = Comparer.Compare(item.Text, 0, itemName, 0, opt);
if (result != 0)
continue;
value = (ushort)item.Value;
return true;
}
value = Item.NONE;
return false;
}
/// <summary>
/// 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>
public static IEnumerable<ComboItem> GetItemsMatching(string itemName, IEnumerable<ComboItem> strings)
{
var opt = GetCompareOption(itemName);
foreach (var item in strings)
{
var result = Comparer.IndexOf(item.Text, itemName, opt);
if (result < 0)
continue;
yield return item;
}
}
/// <summary>
/// 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"/>).
/// </remarks>
/// <param name="itemName">Item name</param>
/// <param name="strings">Item names (and their Item ID values)</param>
public static IEnumerable<ComboItem> GetItemsMatchingOrdered(string itemName, IEnumerable<ComboItem> strings)
{
var matches = GetItemsMatching(itemName, strings);
return GetItemsClosestOrdered(itemName, matches);
}
/// <summary>
/// Gets an enumerable list of item key-value pairs ordered by the closest <see cref="LevenshteinDistance"/> for the requested <see cref="itemName"/>.
/// </summary>
/// <param name="itemName">Item name</param>
/// <param name="strings">Item names (and their Item ID values)</param>
public static IEnumerable<ComboItem> GetItemsClosestOrdered(string itemName, IEnumerable<ComboItem> strings)
{
return strings.OrderBy(z => LevenshteinDistance.Compute(z.Text, itemName));
}
/// <summary>
/// Gets the Item Name and raw 8-byte value as a string.
/// </summary>
/// <param name="item">Item value</param>
public static string GetItemText(Item item)
{
var value = BitConverter.ToUInt64(item.ToBytesClass(), 0);
var name = GameInfo.Strings.GetItemName(item.ItemId);
return $"{name}: {value:X16}";
}
/// <summary>
/// Gets the u16 item ID from the input hex code.
/// </summary>
/// <param name="text">Hex code for the item (preferably 4 digits)</param>
public static ushort GetID(string text)
{
if (!ulong.TryParse(text.Trim(), NumberStyles.AllowHexSpecifier, CultureInfo.CurrentCulture, out var value))
return Item.NONE;
return (ushort)value;
var split = requestHex.Split(SplittersName, StringSplitOptions.RemoveEmptyEntries);
return GetItemsLanguage(split, cfg, type, GameLanguage.DefaultLanguage);
}
}
public enum ItemDestination
/// <summary>
/// Gets a list of DIY item cards from the requested list of DIY IDs.
/// </summary>
/// <remarks>
/// If the first input is a language code (2 characters), the logic will try to parse item names for that language instead of DIY IDs.
/// </remarks>
/// <param name="requestHex">8 byte hex item values (u64 format)</param>
public static IReadOnlyCollection<Item> GetDIYsFromUserInput(string requestHex)
{
PlayerDropped,
FieldItemDropped,
HeldItem,
try
{
// having a language 2char code will cause an exception in parsing; this is fine and is handled by our catch statement.
var split = requestHex.Split(SplittersHex, StringSplitOptions.RemoveEmptyEntries);
return GetDIYItemsHexCode(split);
}
catch
{
var split = requestHex.Split(SplittersName, StringSplitOptions.RemoveEmptyEntries);
return GetDIYItemsLanguage(split);
}
}
/// <summary>
/// Gets a list of items from the requested list of DIY hex code strings.
/// </summary>
/// <remarks>
/// If a hex code parse fails or a recipe ID does not exist, exceptions will be thrown.
/// </remarks>
/// <param name="split">List of recipe IDs as u16 hex</param>
public static IReadOnlyCollection<Item> GetDIYItemsHexCode(IReadOnlyList<string> split)
{
var result = new Item[split.Count];
for (int i = 0; i < result.Length; i++)
{
var text = split[i].Trim();
bool parse = ulong.TryParse(text, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out var value);
if (!parse)
throw new Exception($"Item value out of expected range ({text}).");
if (!RecipeList.Recipes.TryGetValue((ushort)value, out _))
throw new Exception($"DIY recipe appears to be invalid ({text}).");
result[i] = new Item(Item.DIYRecipe) { Count = (ushort)value };
}
return result;
}
/// <summary>
/// Gets a list of DIY item cards from the requested list of item name strings.
/// </summary>
/// <remarks>
/// 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>
public static IReadOnlyCollection<Item> GetDIYItemsLanguage(IReadOnlyList<string> split, string lang = GameLanguage.DefaultLanguage)
{
if (split.Count > 1 && split[0].Length < 3)
{
var langIndex = GameLanguage.GetLanguageIndex(split[0]);
lang = GameLanguage.Language2Char(langIndex);
split = split.Skip(1).ToArray();
}
var result = new Item[split.Count];
for (int i = 0; i < result.Length; i++)
{
var text = split[i].Trim();
var item = GetItem(text, lang);
if (!InvertedRecipeDictionary.TryGetValue(item.ItemId, out var diy))
throw new Exception($"DIY recipe appears to be invalid ({text}).");
result[i] = new Item(Item.DIYRecipe) { Count = diy };
}
return result;
}
/// <summary>
/// Gets a list of items from the requested list of item name strings.
/// </summary>
/// <remarks>
/// 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>
/// <param name="type">Destination where the item will end up at</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>
public static IReadOnlyCollection<Item> GetItemsLanguage(IReadOnlyList<string> split, IConfigItem config, ItemDestination type = ItemDestination.PlayerDropped, string lang = GameLanguage.DefaultLanguage)
{
if (split.Count > 1 && split[0].Length < 3)
{
var langIndex = GameLanguage.GetLanguageIndex(split[0]);
lang = GameLanguage.Language2Char(langIndex);
split = split.Skip(1).ToArray();
}
var strings = GameInfo.Strings.itemlistdisplay;
var result = new Item[split.Count];
for (int i = 0; i < result.Length; i++)
{
var text = split[i].Trim();
var item = CreateItem(text, i, config, type, lang);
if (item.ItemId >= strings.Length)
throw new Exception($"Item requested is out of expected range ({item.ItemId:X4} > {strings.Length:X4}).");
if (string.IsNullOrWhiteSpace(strings[item.ItemId]))
throw new Exception($"Item requested does not have a valid name ({item.ItemId:X4}).");
result[i] = item;
}
return result;
}
/// <summary>
/// Gets a list of items from the requested list of item hex code strings.
/// </summary>
/// <remarks>
/// If a hex code parse fails or a recipe ID does not exist, exceptions will be thrown.
/// </remarks>
/// <param name="split">List of recipe IDs as u16 hex</param>
/// <param name="config">Item packaging options</param>
/// <param name="type">Destination where the item will end up at</param>
public static IReadOnlyCollection<Item> GetItemsHexCode(IReadOnlyList<string> split, IConfigItem config, ItemDestination type = ItemDestination.PlayerDropped)
{
var strings = GameInfo.Strings.itemlistdisplay;
var result = new Item[split.Count];
for (int i = 0; i < result.Length; i++)
{
var text = split[i].Trim();
var convert = GetBytesFromString(text);
var item = CreateItem(convert, i, config, type);
if (item.ItemId >= strings.Length)
throw new Exception($"Item requested is out of expected range ({item.ItemId:X4} > {strings.Length:X4}).");
if (string.IsNullOrWhiteSpace(strings[item.ItemId]))
throw new Exception($"Item requested does not have a valid name ({item.ItemId:X4}).");
result[i] = item;
}
return result;
}
private static byte[] GetBytesFromString(string text)
{
if (!ulong.TryParse(text, NumberStyles.AllowHexSpecifier, CultureInfo.CurrentCulture, out var value))
return Item.NONE.ToBytes();
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")
{
var item = GetItem(name, lang);
if (item.IsNone)
throw new Exception($"Failed to convert item (index {requestIndex}: {name}) for Language {lang}.");
return FinalizeItem(requestIndex, config, type, item);
}
private static Item CreateItem(byte[] convert, int requestIndex, IConfigItem config, ItemDestination type)
{
Item item;
try
{
if (convert.Length != Item.SIZE)
throw new Exception();
item = convert.ToClass<Item>();
}
catch (Exception ex)
{
throw new Exception($"Failed to convert item (index {requestIndex}: {ex.Message}).");
}
return FinalizeItem(requestIndex, config, type, item);
}
private static Item FinalizeItem(int requestIndex, IConfigItem config, ItemDestination type, Item item)
{
if (type == ItemDestination.PlayerDropped)
{
if (!ItemInfo.IsSaneItemForDrop(item) && !config.SkipDropCheck)
throw new Exception($"Unsupported item: (index {requestIndex}).");
if (config.WrapAllItems && item.ShouldWrapItem())
item.SetWrapping(ItemWrapping.WrappingPaper, config.WrappingPaper, true);
}
item.IsDropped = type == ItemDestination.FieldItemDropped;
return item;
}
private static readonly CompareInfo Comparer = CultureInfo.InvariantCulture.CompareInfo;
private const CompareOptions optIncludeSymbols = CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreWidth;
private const CompareOptions optIgnoreSymbols = CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreWidth;
/// <summary>
/// Gets a sensitive compare option, depending on the input string's qualities.
/// </summary>
/// <param name="str">Input string</param>
/// <returns>Default options if no symbols,</returns>
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).
/// </summary>
/// <param name="itemName">Requested Item</param>
/// <param name="lang">Game strings language to fetch with</param>
/// <returns>Returns <see cref="Item.NO_ITEM"/> if no match found.</returns>
public static Item GetItem(string itemName, string lang = "en")
{
var gStrings = GameInfo.GetStrings(lang);
var strings = gStrings.ItemDataSource;
var parsedItem = GetItem(itemName, strings);
if (parsedItem != Item.NO_ITEM)
return parsedItem;
if (gStrings.HasAssociatedItems(itemName, out var items))
{
if (items?.Count == 1)
return new Item((ushort)items[0].Value);
}
return Item.NO_ITEM;
}
/// <summary>
/// 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>
/// <returns>Returns <see cref="Item.NO_ITEM"/> if no match found.</returns>
public static Item GetItem(string itemName, IReadOnlyList<ComboItem> strings)
{
if (TryGetItem(itemName, strings, out var id))
return new Item(id);
return Item.NO_ITEM;
}
/// <summary>
/// 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>
/// <param name="value">Item ID, if found. Otherwise, 0</param>
/// <returns>True if found, false if none.</returns>
public static bool TryGetItem(string itemName, IReadOnlyList<ComboItem> strings, out ushort value)
{
if (TryGetItem(itemName, strings, out value, optIncludeSymbols))
return true;
return TryGetItem(itemName, strings, out value, optIgnoreSymbols);
}
private static bool TryGetItem(string itemName, IEnumerable<ComboItem> strings, out ushort value, CompareOptions opt)
{
foreach (var item in strings)
{
var result = Comparer.Compare(item.Text, 0, itemName, 0, opt);
if (result != 0)
continue;
value = (ushort)item.Value;
return true;
}
value = Item.NONE;
return false;
}
/// <summary>
/// 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>
public static IEnumerable<ComboItem> GetItemsMatching(string itemName, IEnumerable<ComboItem> strings)
{
var opt = GetCompareOption(itemName);
foreach (var item in strings)
{
var result = Comparer.IndexOf(item.Text, itemName, opt);
if (result < 0)
continue;
yield return item;
}
}
/// <summary>
/// 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"/>).
/// </remarks>
/// <param name="itemName">Item name</param>
/// <param name="strings">Item names (and their Item ID values)</param>
public static IEnumerable<ComboItem> GetItemsMatchingOrdered(string itemName, IEnumerable<ComboItem> strings)
{
var matches = GetItemsMatching(itemName, strings);
return GetItemsClosestOrdered(itemName, matches);
}
/// <summary>
/// Gets an enumerable list of item key-value pairs ordered by the closest <see cref="LevenshteinDistance"/> for the requested <see cref="itemName"/>.
/// </summary>
/// <param name="itemName">Item name</param>
/// <param name="strings">Item names (and their Item ID values)</param>
public static IEnumerable<ComboItem> GetItemsClosestOrdered(string itemName, IEnumerable<ComboItem> strings)
{
return strings.OrderBy(z => LevenshteinDistance.Compute(z.Text, itemName));
}
/// <summary>
/// Gets the Item Name and raw 8-byte value as a string.
/// </summary>
/// <param name="item">Item value</param>
public static string GetItemText(Item item)
{
var value = ReadUInt64LittleEndian(item.ToBytesClass());
var name = GameInfo.Strings.GetItemName(item.ItemId);
return $"{name}: {value:X16}";
}
/// <summary>
/// Gets the u16 item ID from the input hex code.
/// </summary>
/// <param name="text">Hex code for the item (preferably 4 digits)</param>
public static ushort GetID(string text)
{
if (!ulong.TryParse(text.Trim(), NumberStyles.AllowHexSpecifier, CultureInfo.CurrentCulture, out var value))
return Item.NONE;
return (ushort)value;
}
}
public enum ItemDestination
{
PlayerDropped,
FieldItemDropped,
HeldItem,
}

View File

@ -1,50 +1,49 @@
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
/// https://stackoverflow.com/a/13793600
/// </summary>
public static int Compute(string s, string t)
{
/// <summary>
/// Compute the distance between two strings.
/// http://www.dotnetperls.com/levenshtein
/// https://stackoverflow.com/a/13793600
/// </summary>
public static int Compute(string s, string t)
int n = s.Length;
int m = t.Length;
int[,] d = new int[n + 1, m + 1];
// Step 1
if (n == 0)
return m;
if (m == 0)
return n;
// Step 2
for (int i = 0; i <= n; d[i, 0] = i++) { }
for (int j = 0; j <= m; d[0, j] = j++) { }
// Step 3
for (int i = 1; i <= n; i++)
{
int n = s.Length;
int m = t.Length;
int[,] d = new int[n + 1, m + 1];
// Step 1
if (n == 0)
return m;
if (m == 0)
return n;
// Step 2
for (int i = 0; i <= n; d[i, 0] = i++) { }
for (int j = 0; j <= m; d[0, j] = j++) { }
// Step 3
for (int i = 1; i <= n; i++)
//Step 4
for (int j = 1; j <= m; j++)
{
//Step 4
for (int j = 1; j <= m; j++)
{
// Step 5
int cost = (t[j - 1] == s[i - 1]) ? 0 : 1;
// Step 5
int cost = (t[j - 1] == s[i - 1]) ? 0 : 1;
// Step 6
var x1 = d[i - 1, j] + 1;
var x2 = d[i, j - 1] + 1;
var x3 = d[i - 1, j - 1] + cost;
d[i, j] = Math.Min(Math.Min(x1, x2), x3);
}
// Step 6
var x1 = d[i - 1, j] + 1;
var x2 = d[i, j - 1] + 1;
var x3 = d[i - 1, j - 1] + cost;
d[i, j] = Math.Min(Math.Min(x1, x2), x3);
}
// Step 7
return d[n, m];
}
// Step 7
return d[n, m];
}
}
}

View File

@ -2,122 +2,128 @@
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)
private readonly byte[] _counter;
private readonly Aes _aes = GetAes();
// 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 static Aes GetAes()
{
private readonly byte[] _counter;
private readonly AesManaged _aes = new() {Mode = CipherMode.ECB, Padding = PaddingMode.None};
public Aes128CounterMode(byte[] counter)
{
const int expect = 0x10;
if (counter.Length != expect)
throw new ArgumentException($"Counter size must be same as block size (actual: {counter.Length}, expected: {expect})");
_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 void GenerateKey() => _aes.GenerateKey();
public override void GenerateIV() { /* IV not needed in Counter Mode */ }
protected override void Dispose(bool disposing) => _aes.Dispose();
var result = Aes.Create();
result.Mode = CipherMode.ECB;
result.Padding = PaddingMode.None;
return result;
}
public sealed class CounterModeCryptoTransform : ICryptoTransform
public Aes128CounterMode(byte[] counter)
{
private readonly byte[] _counter;
private readonly ICryptoTransform _counterEncryptor;
private readonly Queue<byte> _xorMask = new();
private readonly SymmetricAlgorithm _symmetricAlgorithm;
public CounterModeCryptoTransform(SymmetricAlgorithm symmetricAlgorithm, byte[] key, byte[] counter)
{
if (counter.Length != symmetricAlgorithm.BlockSize / 8)
throw new ArgumentException($"Counter size must be same as block size (actual: {counter.Length}, expected: {symmetricAlgorithm.BlockSize / 8})");
_symmetricAlgorithm = symmetricAlgorithm;
_encryptOutput = new byte[counter.Length];
_counter = counter;
var zeroIv = new byte[counter.Length];
_counterEncryptor = symmetricAlgorithm.CreateEncryptor(key, zeroIv);
}
public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
{
var output = new byte[inputCount];
TransformBlock(inputBuffer, inputOffset, inputCount, output, 0);
return output;
}
public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
{
var xm = _xorMask;
for (var i = 0; i < inputCount; i++)
{
if (xm.Count == 0)
EncryptCounterThenIncrement();
var mask = xm.Dequeue();
outputBuffer[outputOffset + i] = (byte)(inputBuffer[inputOffset + i] ^ mask);
}
return inputCount;
}
private readonly byte[] _encryptOutput;
private void EncryptCounterThenIncrement()
{
var counterModeBlock = _encryptOutput;
_counterEncryptor.TransformBlock(_counter, 0, _counter.Length, counterModeBlock, 0);
IncrementCounter();
var xm = _xorMask;
foreach (var b in counterModeBlock)
xm.Enqueue(b);
}
private void IncrementCounter()
{
var ctr = _counter;
for (var i = ctr.Length - 1; i >= 0; i--)
{
if (++ctr[i] != 0)
break;
}
}
public int InputBlockSize => _symmetricAlgorithm.BlockSize / 8;
public int OutputBlockSize => _symmetricAlgorithm.BlockSize / 8;
public bool CanTransformMultipleBlocks => true;
public bool CanReuseTransform => false;
public void Dispose() => _counterEncryptor.Dispose();
const int expect = 0x10;
if (counter.Length != expect)
throw new ArgumentException($"Counter size must be same as block size (actual: {counter.Length}, expected: {expect})");
_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 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
{
private readonly byte[] _counter;
private readonly ICryptoTransform _counterEncryptor;
private readonly Queue<byte> _xorMask = new();
private readonly SymmetricAlgorithm _symmetricAlgorithm;
public CounterModeCryptoTransform(SymmetricAlgorithm symmetricAlgorithm, byte[] key, byte[] counter)
{
if (counter.Length != symmetricAlgorithm.BlockSize / 8)
throw new ArgumentException($"Counter size must be same as block size (actual: {counter.Length}, expected: {symmetricAlgorithm.BlockSize / 8})");
_symmetricAlgorithm = symmetricAlgorithm;
_encryptOutput = new byte[counter.Length];
_counter = counter;
var zeroIv = new byte[counter.Length];
_counterEncryptor = symmetricAlgorithm.CreateEncryptor(key, zeroIv);
}
public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
{
var output = new byte[inputCount];
TransformBlock(inputBuffer, inputOffset, inputCount, output, 0);
return output;
}
public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
{
var xm = _xorMask;
for (var i = 0; i < inputCount; i++)
{
if (xm.Count == 0)
EncryptCounterThenIncrement();
var mask = xm.Dequeue();
outputBuffer[outputOffset + i] = (byte)(inputBuffer[inputOffset + i] ^ mask);
}
return inputCount;
}
private readonly byte[] _encryptOutput;
private void EncryptCounterThenIncrement()
{
var counterModeBlock = _encryptOutput;
_counterEncryptor.TransformBlock(_counter, 0, _counter.Length, counterModeBlock, 0);
IncrementCounter();
var xm = _xorMask;
foreach (var b in counterModeBlock)
xm.Enqueue(b);
}
private void IncrementCounter()
{
var ctr = _counter;
for (var i = ctr.Length - 1; i >= 0; i--)
{
if (++ctr[i] != 0)
break;
}
}
public int InputBlockSize => _symmetricAlgorithm.BlockSize / 8;
public int OutputBlockSize => _symmetricAlgorithm.BlockSize / 8;
public bool CanTransformMultipleBlocks => true;
public bool CanReuseTransform => false;
public void Dispose() => _counterEncryptor.Dispose();
}

View File

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

View File

@ -1,81 +1,81 @@
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.
private const byte SHIFT_BASE = 3;
public readonly uint OriginalEncrypted;
public readonly ushort Adjust;
public readonly byte Shift;
public readonly byte Checksum;
public uint Value;
public override string ToString() => Value.ToString();
public EncryptedInt32(uint encryptedValue, ushort adjust = 0, byte shift = 0, byte checksum = 0)
{
// Encryption constant used to encrypt the int.
private const uint ENCRYPTION_CONSTANT = 0x80E32B11;
// Base shift count used in the encryption.
private const byte SHIFT_BASE = 3;
public readonly uint OriginalEncrypted;
public readonly ushort Adjust;
public readonly byte Shift;
public readonly byte Checksum;
public uint Value;
public override string ToString() => Value.ToString();
public EncryptedInt32(uint encryptedValue, ushort adjust = 0, byte shift = 0, byte checksum = 0)
{
OriginalEncrypted = encryptedValue;
Adjust = adjust;
Shift = shift;
Checksum = checksum;
Value = Decrypt(encryptedValue, shift, adjust);
}
public void Write(byte[] data, int offset) => Write(this, data, offset);
// Calculates a checksum for a given encrypted value
// Checksum calculation is every byte of the encrypted in added together minus 0x2D.
public static byte CalculateChecksum(uint value)
{
var byteSum = value + (value >> 16) + (value >> 24) + (value >> 8);
return (byte)(byteSum - 0x2D);
}
public static uint Decrypt(uint encrypted, byte shift, ushort adjust)
{
// Decrypt the encrypted int using the given params.
ulong val = ((ulong) encrypted) << ((32 - SHIFT_BASE - shift) & 0x3F);
val += val >> 32;
return ENCRYPTION_CONSTANT - adjust + (uint)val;
}
public static uint Encrypt(uint value, byte shift, ushort adjust)
{
ulong val = (ulong) (value + (adjust - ENCRYPTION_CONSTANT)) << (shift + SHIFT_BASE);
return (uint) ((val >> 32) + val);
}
public static EncryptedInt32 ReadVerify(byte[] data, int 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)
{
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);
}
public static void Write(EncryptedInt32 value, byte[] data, int offset)
{
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;
}
OriginalEncrypted = encryptedValue;
Adjust = adjust;
Shift = shift;
Checksum = checksum;
Value = Decrypt(encryptedValue, shift, adjust);
}
}
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.
public static byte CalculateChecksum(uint value)
{
var byteSum = value + (value >> 16) + (value >> 24) + (value >> 8);
return (byte)(byteSum - 0x2D);
}
public static uint Decrypt(uint encrypted, byte shift, ushort adjust)
{
// Decrypt the encrypted int using the given params.
ulong val = ((ulong) encrypted) << ((32 - SHIFT_BASE - shift) & 0x3F);
val += val >> 32;
return ENCRYPTION_CONSTANT - adjust + (uint)val;
}
public static uint Encrypt(uint value, byte shift, ushort adjust)
{
ulong val = (ulong) (value + unchecked(adjust - ENCRYPTION_CONSTANT)) << (shift + SHIFT_BASE);
return (uint) ((val >> 32) + val);
}
public static EncryptedInt32 ReadVerify(ReadOnlySpan<byte> data, int 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(ReadOnlySpan<byte> data)
{
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, Span<byte> data)
{
var encrypted = Encrypt(value.Value, value.Shift, value.Adjust);
WriteUInt32LittleEndian(data, encrypted);
WriteUInt16LittleEndian(data[4..], value.Adjust);
data[6] = value.Shift;
data[7] = CalculateChecksum(encrypted);
}
}

View File

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

View File

@ -1,82 +1,81 @@
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++)
rand.GetU64();
var rndRollCount = (prms & 0xF) + 1;
for (var i = 0; i < rndRollCount; i++)
rand.GetU64();
var result = new byte[0x10];
for (var i = 0; i < result.Length; i++)
result[i] = (byte)(rand.GetU32() >> 24);
var result = new byte[0x10];
for (var i = 0; i < result.Length; i++)
result[i] = (byte)(rand.GetU32() >> 24);
return result;
}
/// <summary>
/// Decrypts the <see cref="encData"/> using the <see cref="headerData"/> in place.
/// </summary>
/// <param name="headerData">Header Data</param>
/// <param name="encData">Encrypted SaveData</param>
public static void Decrypt(byte[] headerData, byte[] encData)
{
// First 256 bytes go unused
var importantData = new uint[0x80];
Buffer.BlockCopy(headerData, 0x100, importantData, 0, 0x200);
// Set up Key
var key = GetParam(importantData, 0);
// Set up counter
var counter = GetParam(importantData, 2);
// Do the AES
using var aesCtr = new Aes128CounterMode(counter);
var transform = aesCtr.CreateDecryptor(key, counter);
transform.TransformBlock(encData, 0, encData.Length, encData, 0);
}
private static CryptoFile GenerateHeaderFile(uint seed, byte[] versionData)
{
// Generate 128 Random uints which will be used for params
var random = new XorShift128(seed);
var encryptData = new uint[128];
for (var i = 0; i < encryptData.Length; i++)
encryptData[i] = random.GetU32();
var headerData = new byte[0x300];
Buffer.BlockCopy(versionData, 0, headerData, 0, 0x100);
Buffer.BlockCopy(encryptData, 0, headerData, 0x100, 0x200);
return new CryptoFile(headerData, GetParam(encryptData, 0), GetParam(encryptData, 2));
}
/// <summary>
/// Encrypts the <see cref="data"/> (savedata) using the provided <see cref="seed"/>.
/// </summary>
/// <param name="data">SaveData to encrypt</param>
/// <param name="seed">Seed to encrypt with</param>
/// <param name="versionData">Version data to encrypt with</param>
/// <returns>Encrypted SaveData, and associated headerData</returns>
public static EncryptedSaveFile Encrypt(byte[] data, uint seed, byte[] versionData)
{
// Generate header file and get key and counter
var header = GenerateHeaderFile(seed, versionData);
// Encrypt file
using var aesCtr = new Aes128CounterMode(header.Ctr);
var transform = aesCtr.CreateEncryptor(header.Key, header.Ctr);
var encData = new byte[data.Length];
transform.TransformBlock(data, 0, data.Length, encData, 0);
return new EncryptedSaveFile(encData, header.Data);
}
return result;
}
}
/// <summary>
/// Decrypts the <see cref="encData"/> using the <see cref="headerData"/> in place.
/// </summary>
/// <param name="headerData">Header Data</param>
/// <param name="encData">Encrypted SaveData</param>
public static void Decrypt(byte[] headerData, byte[] encData)
{
// First 256 bytes go unused
var importantData = new uint[0x80];
Buffer.BlockCopy(headerData, 0x100, importantData, 0, 0x200);
// Set up Key
var key = GetParam(importantData, 0);
// Set up counter
var counter = GetParam(importantData, 2);
// Do the AES
using var aesCtr = new Aes128CounterMode(counter);
var transform = aesCtr.CreateDecryptor(key, counter);
transform.TransformBlock(encData, 0, encData.Length, encData, 0);
}
private static CryptoFile GenerateHeaderFile(uint seed, byte[] versionData)
{
// Generate 128 Random uints which will be used for params
var random = new XorShift128(seed);
var encryptData = new uint[128];
for (var i = 0; i < encryptData.Length; i++)
encryptData[i] = random.GetU32();
var headerData = new byte[0x300];
Buffer.BlockCopy(versionData, 0, headerData, 0, 0x100);
Buffer.BlockCopy(encryptData, 0, headerData, 0x100, 0x200);
return new CryptoFile(headerData, GetParam(encryptData, 0), GetParam(encryptData, 2));
}
/// <summary>
/// Encrypts the <see cref="data"/> (savedata) using the provided <see cref="seed"/>.
/// </summary>
/// <param name="data">SaveData to encrypt</param>
/// <param name="seed">Seed to encrypt with</param>
/// <param name="versionData">Version data to encrypt with</param>
/// <returns>Encrypted SaveData, and associated headerData</returns>
public static EncryptedSaveFile Encrypt(byte[] data, uint seed, byte[] versionData)
{
// Generate header file and get key and counter
var header = GenerateHeaderFile(seed, versionData);
// Encrypt file
using var aesCtr = new Aes128CounterMode(header.Ctr);
var transform = aesCtr.CreateEncryptor(header.Key, header.Ctr);
var encData = new byte[data.Length];
transform.TransformBlock(data, 0, data.Length, encData, 0);
return new EncryptedSaveFile(encData, header.Data);
}
}

View File

@ -1,32 +1,31 @@
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"/>.
/// Name of the File that these <see cref="HashRegions"/> apply to.
/// </summary>
public sealed class FileHashDetails
public readonly string FileName;
/// <summary>
/// Expected file size of the <see cref="FileName"/>.
/// </summary>
public readonly uint FileSize;
/// <summary>
/// Hash specs that are done in this file.
/// </summary>
public readonly IReadOnlyList<FileHashRegion> HashRegions;
public FileHashDetails(string fileName, uint fileSize, IReadOnlyList<FileHashRegion> regions)
{
/// <summary>
/// Name of the File that these <see cref="HashRegions"/> apply to.
/// </summary>
public readonly string FileName;
/// <summary>
/// Expected file size of the <see cref="FileName"/>.
/// </summary>
public readonly uint FileSize;
/// <summary>
/// Hash specs that are done in this file.
/// </summary>
public readonly IReadOnlyList<FileHashRegion> HashRegions;
public FileHashDetails(string fileName, uint fileSize, IReadOnlyList<FileHashRegion> regions)
{
FileName = fileName;
FileSize = fileSize;
HashRegions = regions;
}
FileName = fileName;
FileSize = fileSize;
HashRegions = regions;
}
}

View File

@ -1,25 +1,24 @@
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) { }
public FileHashInfo(IEnumerable<FileHashDetails> hashSets)
{
private readonly IReadOnlyDictionary<uint, FileHashDetails> List;
public FileHashInfo(FileHashInfo dupe) : this(dupe.List.Values) { }
public FileHashInfo(IEnumerable<FileHashDetails> hashSets)
{
var list = new Dictionary<uint, FileHashDetails>();
foreach (var hashSet in hashSets)
list[hashSet.FileSize] = hashSet;
List = list;
}
public FileHashDetails? GetFile(string nameData)
{
return List.Values.FirstOrDefault(z => z.FileName == nameData);
}
var list = new Dictionary<uint, FileHashDetails>();
foreach (var hashSet in hashSets)
list[hashSet.FileSize] = hashSet;
List = list;
}
}
public FileHashDetails? GetFile(string nameData)
{
return List.Values.FirstOrDefault(z => z.FileName == nameData);
}
}

View File

@ -1,55 +1,21 @@
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.
/// Offset where the data to be hashed starts at (calculated).
/// </summary>
public readonly struct FileHashRegion
{
/// <summary>
/// Offset of the calculated hash.
/// </summary>
public readonly int HashOffset;
public int BeginOffset => HashOffset + 4;
/// <summary>
/// Length of the hashed data.
/// </summary>
public readonly int Size;
/// <summary>
/// Offset where the data to be hashed ends at (calculated).
/// </summary>
public int EndOffset => BeginOffset + Size;
/// <summary>
/// Offset where the data to be hashed starts at (calculated).
/// </summary>
public int BeginOffset => HashOffset + 4;
/// <summary>
/// Offset where the data to be hashed ends at (calculated).
/// </summary>
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
}
public override string ToString() => $"0x{HashOffset:X}: (0x{BeginOffset:X}-0x{EndOffset:X})";
}

File diff suppressed because it is too large Load Diff

View File

@ -1,85 +1,77 @@
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)
{
private static uint Murmur32_Scramble(uint k)
{
k = (k * 0x16A88000) | ((k * 0xCC9E2D51) >> 17);
k *= 0x1B873593;
return k;
}
/// <summary>
/// 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)
{
uint checksum = seed;
if (size > 3)
{
for (var i = 0; i < (size / sizeof(uint)); i++)
{
var val = BitConverter.ToUInt32(data, offset);
checksum ^= Murmur32_Scramble(val);
checksum = (checksum >> 19) | (checksum << 13);
checksum = (checksum * 5) + 0xE6546B64;
offset += 4;
}
}
var remainder = size % sizeof(uint);
if (remainder != 0)
{
uint val = BitConverter.ToUInt32(data, (int)((offset + size) - remainder));
for (var i = 0; i < (sizeof(uint) - remainder); i++)
val >>= 8;
checksum ^= Murmur32_Scramble(val);
}
checksum ^= size;
checksum ^= checksum >> 16;
checksum *= 0x85EBCA6B;
checksum ^= checksum >> 13;
checksum *= 0xC2B2AE35;
checksum ^= checksum >> 16;
return checksum;
}
/// <summary>
/// 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>
/// <returns>Calculated hash that was written back to the data.</returns>
public static uint UpdateMurmur32(byte[] data, int hashOffset, int readOffset, uint readSize)
{
var newHash = GetMurmur3Hash(data, readOffset, readSize);
var hashBytes = BitConverter.GetBytes(newHash);
hashBytes.CopyTo(data, hashOffset);
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);
k = (k * 0x16A88000) | ((k * 0xCC9E2D51) >> 17);
k *= 0x1B873593;
return k;
}
}
/// <summary>
/// Updates the hash at the specified offset, using the input parameters.
/// </summary>
/// <param name="data">Data to hash</param>
/// <param name="seed">Initial Murmur seed (optional)</param>
/// <returns>Calculated hash.</returns>
public static uint GetMurmur3Hash(ReadOnlySpan<byte> data, uint seed = 0)
{
var checksum = seed;
var remaining = data;
while (remaining.Length >= sizeof(uint))
{
var val = ReadUInt32LittleEndian(remaining);
checksum ^= Murmur32_Scramble(val);
checksum = (checksum >> 19) | (checksum << 13);
checksum = (checksum * 5) + 0xE6546B64;
remaining = remaining[sizeof(uint)..];
}
if (!remaining.IsEmpty)
{
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 ^= (uint)data.Length;
checksum ^= checksum >> 16;
checksum *= 0x85EBCA6B;
checksum ^= checksum >> 13;
checksum *= 0xC2B2AE35;
checksum ^= checksum >> 16;
return checksum;
}
/// <summary>
/// Updates the hash at the specified offset, using the input parameters.
/// </summary>
/// <param name="data">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(ReadOnlySpan<byte> data, Span<byte> hashDestination)
{
var newHash = GetMurmur3Hash(data);
WriteUInt32LittleEndian(hashDestination, newHash);
return newHash;
}
}

View File

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

View File

@ -1,266 +1,262 @@
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
{
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 IVillager GetVillager(int index) => Offsets.ReadVillager(Data, index);
public void SetVillager(IVillager value, int index) => Offsets.WriteVillager(value, Data, index);
public IVillagerHouse GetVillagerHouse(int index) => Offsets.ReadVillagerHouse(Data, index);
public void SetVillagerHouse(IVillagerHouse value, int index) => Offsets.WriteVillagerHouse(value, Data, index);
public IVillager[] GetVillagers()
{
var villagers = new IVillager[MainSaveOffsets.VillagerCount];
for (int i = 0; i < villagers.Length; i++)
villagers[i] = GetVillager(i);
return villagers;
}
public void SetVillagers(IReadOnlyList<IVillager> villagers)
{
for (int i = 0; i < villagers.Count; i++)
SetVillager(villagers[i], i);
}
public IVillagerHouse[] GetVillagerHouses()
{
var villagers = new IVillagerHouse[MainSaveOffsets.VillagerCount];
for (int i = 0; i < villagers.Length; i++)
villagers[i] = GetVillagerHouse(i);
return villagers;
}
public void SetVillagerHouses(IReadOnlyList<IVillagerHouse> villagers)
{
for (int i = 0; i < villagers.Count; i++)
SetVillagerHouse(villagers[i], i);
}
public DesignPattern GetDesign(int index) => Offsets.ReadPattern(Data, index);
public void SetDesign(DesignPattern value, int index, byte[] playerID, 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 IReadOnlyList<Item> RecycleBin
{
get => Item.GetArray(Data.Slice(Offsets.LostItemBox, MainSaveOffsets.RecycleBinCount * Item.SIZE));
set => Item.SetArray(value).CopyTo(Data, Offsets.LostItemBox);
}
public IReadOnlyList<Building> Buildings
{
get => Building.GetArray(Data.Slice(Offsets.MainFieldStructure, MainSaveOffsets.BuildingCount * Building.SIZE));
set => Building.SetArray(value).CopyTo(Data, Offsets.MainFieldStructure);
}
public IPlayerHouse GetPlayerHouse(int index) => Offsets.ReadPlayerHouse(Data, index);
public void SetPlayerHouse(IPlayerHouse value, int index) => Offsets.WritePlayerHouse(value, Data, index);
public IPlayerHouse[] GetPlayerHouses()
{
var players = new IPlayerHouse[MainSaveOffsets.PlayerCount];
for (int i = 0; i < players.Length; i++)
players[i] = GetPlayerHouse(i);
return players;
}
public void SetPlayerHouses(IReadOnlyList<IPlayerHouse> houses)
{
for (int i = 0; i < houses.Count; i++)
SetPlayerHouse(houses[i], i);
}
public DesignPattern[] GetDesigns()
{
var result = new DesignPattern[Offsets.PatternCount];
for (int i = 0; i <result.Length; i++)
result[i] = GetDesign(i);
return result;
}
public void SetDesigns(IReadOnlyList<DesignPattern> value, byte[] playerID, byte[] townID)
{
var count = Math.Min(Offsets.PatternCount, value.Count);
for (int i = 0; i < count; i++)
SetDesign(value[i], i, playerID, townID);
}
public DesignPatternPRO[] GetDesignsPRO()
{
var result = new DesignPatternPRO[Offsets.PatternCount];
for (int i = 0; i < result.Length; i++)
result[i] = GetDesignPRO(i);
return result;
}
public void SetDesignsPRO(IReadOnlyList<DesignPatternPRO> value, byte[] playerID, byte[] townID)
{
var count = Math.Min(Offsets.PatternCount, value.Count);
for (int i = 0; i < count; i++)
SetDesignPRO(value[i], i, playerID, townID);
}
public DesignPattern FlagMyDesign
{
get => MainSaveOffsets.ReadPatternAtOffset(Data, Offsets.PatternFlag);
set => value.Data.CopyTo(Data, Offsets.PatternFlag);
}
public DesignPatternPRO[] GetDesignsTailor()
{
var result = new DesignPatternPRO[MainSaveOffsets.PatternTailorCount];
for (int i = 0; i < result.Length; i++)
result[i] = MainSaveOffsets.ReadPatternPROAtOffset(Data, Offsets.PatternTailor + (i * DesignPatternPRO.SIZE));
return result;
}
public void SetDesignsTailor(IReadOnlyList<DesignPatternPRO> value)
{
var count = Math.Min(Offsets.PatternCount, value.Count);
for (int i = 0; i < count; i++)
value[i].Data.CopyTo(Data, Offsets.PatternTailor + (i * DesignPatternPRO.SIZE));
}
private const int EventFlagsSaveCount = 0x400;
public 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 TurnipStonk Turnips
{
get => Data.Slice(Offsets.ShopKabu, TurnipStonk.SIZE).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);
}
public const int AcreWidth = 7 + (2 * 1); // 1 on each side cannot be traversed
private const int AcreHeight = 6 + (2 * 1); // 1 on each side cannot be traversed
private const int AcreMax = AcreWidth * AcreHeight;
private const int AcreSizeAll = AcreMax * 2;
public ushort GetAcre(int index)
{
if ((uint)index > AcreMax)
throw new ArgumentOutOfRangeException(nameof(index));
return BitConverter.ToUInt16(Data, 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));
}
public byte[] GetAcreBytes() => Data.Slice(Offsets.OutsideField, AcreSizeAll);
public void SetAcreBytes(byte[] data)
{
if (data.Length != AcreSizeAll)
throw new ArgumentOutOfRangeException(nameof(data.Length));
data.CopyTo(Data, 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 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;
}
public void SetMapDesignTiles(ushort[] value)
{
Buffer.BlockCopy(value, 0, Data, Offsets.MyDesignMap, sizeof(ushort) * value.Length);
}
private const int FieldItemLayerSize = MapGrid.MapTileCount32x32 * Item.SIZE;
private const int FieldItemFlagSize = MapGrid.MapTileCount32x32 / 8; // bitflags
private int FieldItemLayer1 => Offsets.FieldItem;
private int FieldItemLayer2 => Offsets.FieldItem + FieldItemLayerSize;
public int FieldItemFlag1 => Offsets.FieldItem + (FieldItemLayerSize * 2);
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 Item[] GetFieldItemLayer2() => Item.GetArray(Data.Slice(FieldItemLayer2, FieldItemLayerSize));
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);
}
public ushort MainFieldParamUniqueID
{
get => BitConverter.ToUInt16(Data, Offsets.OutsideField + AcreSizeAll + 2);
set => BitConverter.GetBytes(value).CopyTo(Data, Offsets.OutsideField + AcreSizeAll + 2);
}
public uint EventPlazaLeftUpX
{
get => BitConverter.ToUInt32(Data, Offsets.OutsideField + AcreSizeAll + 4);
set => BitConverter.GetBytes(value).CopyTo(Data, Offsets.OutsideField + AcreSizeAll + 4);
}
public uint EventPlazaLeftUpZ
{
get => BitConverter.ToUInt32(Data, Offsets.OutsideField + AcreSizeAll + 8);
set => BitConverter.GetBytes(value).CopyTo(Data, Offsets.OutsideField + AcreSizeAll + 8);
}
public GSaveVisitorNpc Visitor
{
get => Data.ToClass<GSaveVisitorNpc>(Offsets.Visitor, GSaveVisitorNpc.SIZE);
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);
}
public GSaveTime LastSaved => Data.Slice(Offsets.LastSavedTime, GSaveTime.SIZE).ToStructure<GSaveTime>();
public GSaveBulletinBoard Bulletin
{
get => Data.Slice(Offsets.BulletinBoard, GSaveBulletinBoard.SIZE).ToStructure<GSaveBulletinBoard>();
set => value.ToBytes().CopyTo(Data, Offsets.BulletinBoard);
}
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);
public IVillagerHouse GetVillagerHouse(int index) => Offsets.ReadVillagerHouse(Data, index);
public void SetVillagerHouse(IVillagerHouse value, int index) => Offsets.WriteVillagerHouse(value, Data, index);
public IVillager[] GetVillagers()
{
var villagers = new IVillager[MainSaveOffsets.VillagerCount];
for (int i = 0; i < villagers.Length; i++)
villagers[i] = GetVillager(i);
return villagers;
}
public void SetVillagers(IReadOnlyList<IVillager> villagers)
{
for (int i = 0; i < villagers.Count; i++)
SetVillager(villagers[i], i);
}
public IVillagerHouse[] GetVillagerHouses()
{
var villagers = new IVillagerHouse[MainSaveOffsets.VillagerCount];
for (int i = 0; i < villagers.Length; i++)
villagers[i] = GetVillagerHouse(i);
return villagers;
}
public void SetVillagerHouses(IReadOnlyList<IVillagerHouse> villagers)
{
for (int i = 0; i < villagers.Count; i++)
SetVillagerHouse(villagers[i], i);
}
public DesignPattern GetDesign(int index) => Offsets.ReadPattern(Data, index);
public void SetDesign(DesignPattern value, int index, 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, 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..]);
}
public IReadOnlyList<Building> Buildings
{
get => Building.GetArray(Data.Slice(Offsets.MainFieldStructure, MainSaveOffsets.BuildingCount * Building.SIZE));
set => Building.SetArray(value).CopyTo(Data[Offsets.MainFieldStructure..]);
}
public IPlayerHouse GetPlayerHouse(int index) => Offsets.ReadPlayerHouse(Data, index);
public void SetPlayerHouse(IPlayerHouse value, int index) => Offsets.WritePlayerHouse(value, Data, index);
public IPlayerHouse[] GetPlayerHouses()
{
var players = new IPlayerHouse[MainSaveOffsets.PlayerCount];
for (int i = 0; i < players.Length; i++)
players[i] = GetPlayerHouse(i);
return players;
}
public void SetPlayerHouses(IReadOnlyList<IPlayerHouse> houses)
{
for (int i = 0; i < houses.Count; i++)
SetPlayerHouse(houses[i], i);
}
public DesignPattern[] GetDesigns()
{
var result = new DesignPattern[Offsets.PatternCount];
for (int i = 0; i <result.Length; i++)
result[i] = GetDesign(i);
return result;
}
public void SetDesigns(IReadOnlyList<DesignPattern> value, Span<byte> playerID, Span<byte> townID)
{
var count = Math.Min(Offsets.PatternCount, value.Count);
for (int i = 0; i < count; i++)
SetDesign(value[i], i, playerID, townID);
}
public DesignPatternPRO[] GetDesignsPRO()
{
var result = new DesignPatternPRO[Offsets.PatternCount];
for (int i = 0; i < result.Length; i++)
result[i] = GetDesignPRO(i);
return result;
}
public void SetDesignsPRO(IReadOnlyList<DesignPatternPRO> value, Span<byte> playerID, Span<byte> townID)
{
var count = Math.Min(Offsets.PatternCount, value.Count);
for (int i = 0; i < count; i++)
SetDesignPRO(value[i], i, playerID, townID);
}
public DesignPattern FlagMyDesign
{
get => MainSaveOffsets.ReadPatternAtOffset(Data, Offsets.PatternFlag);
set => value.Data.CopyTo(Data[Offsets.PatternFlag..]);
}
public DesignPatternPRO[] GetDesignsTailor()
{
var result = new DesignPatternPRO[MainSaveOffsets.PatternTailorCount];
for (int i = 0; i < result.Length; i++)
result[i] = MainSaveOffsets.ReadPatternPROAtOffset(Data, Offsets.PatternTailor + (i * DesignPatternPRO.SIZE));
return result;
}
public void SetDesignsTailor(IReadOnlyList<DesignPatternPRO> value)
{
var count = Math.Min(Offsets.PatternCount, value.Count);
for (int i = 0; i < count; i++)
value[i].Data.CopyTo(Data.Slice(Offsets.PatternTailor + (i * DesignPatternPRO.SIZE)));
}
private const int EventFlagsSaveCount = 0x400;
public Span<short> EventFlagLand => MemoryMarshal.Cast<byte, short>(Data.Slice(Offsets.EventFlagLand, sizeof(short) * EventFlagsSaveCount));
public TurnipStonk Turnips
{
get => Data.Slice(Offsets.ShopKabu, TurnipStonk.SIZE).ToArray().ToClass<TurnipStonk>();
set => value.ToBytesClass().CopyTo(Data[Offsets.ShopKabu..]);
}
public Museum Museum
{
get => new(Data.Slice(Offsets.Museum, Museum.SIZE).ToArray());
set => value.Data.CopyTo(Data[Offsets.Museum..]);
}
public const int AcreWidth = 7 + (2 * 1); // 1 on each side cannot be traversed
private const int AcreHeight = 6 + (2 * 1); // 1 on each side cannot be traversed
private const int AcreMax = AcreWidth * AcreHeight;
private const int AcreSizeAll = AcreMax * 2;
public ushort GetAcre(int index)
{
if ((uint)index > AcreMax)
throw new ArgumentOutOfRangeException(nameof(index));
return ReadUInt16LittleEndian(Data.Slice(Offsets.OutsideField + (index * 2)));
}
public void SetAcre(int index, ushort value)
{
if ((uint)index > AcreMax)
throw new ArgumentOutOfRangeException(nameof(index));
WriteUInt16LittleEndian(Data.Slice(Offsets.OutsideField + (index * 2)), value);
}
public byte[] GetAcreBytes() => Data.Slice(Offsets.OutsideField, AcreSizeAll).ToArray();
public void SetAcreBytes(ReadOnlySpan<byte> data)
{
if (data.Length != AcreSizeAll)
throw new ArgumentOutOfRangeException(nameof(data.Length));
data.CopyTo(Data.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 const int MapDesignNone = 0xF800;
public ushort[] GetMapDesignTiles()
{
var slice = Data.Slice(Offsets.MyDesignMap, 112 * 96 * sizeof(ushort));
return MemoryMarshal.Cast<byte, ushort>(slice).ToArray();
}
public void SetMapDesignTiles(ReadOnlySpan<ushort> value)
{
var cast = MemoryMarshal.Cast<ushort, byte>(value);
cast.CopyTo(Data[Offsets.MyDesignMap..]);
}
private const int FieldItemLayerSize = MapGrid.MapTileCount32x32 * Item.SIZE;
private const int FieldItemFlagSize = MapGrid.MapTileCount32x32 / 8; // bitflags
private int FieldItemLayer1 => Offsets.FieldItem;
private int FieldItemLayer2 => Offsets.FieldItem + FieldItemLayerSize;
public int FieldItemFlag1 => Offsets.FieldItem + (FieldItemLayerSize * 2);
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 Item[] GetFieldItemLayer2() => Item.GetArray(Data.Slice(FieldItemLayer2, FieldItemLayerSize));
public void SetFieldItemLayer2(IReadOnlyList<Item> array) => Item.SetArray(array).CopyTo(Data[FieldItemLayer2..]);
public ushort OutsideFieldTemplateUniqueId
{
get => ReadUInt16LittleEndian(Data[(Offsets.OutsideField + AcreSizeAll)..]);
set => WriteUInt16LittleEndian(Data[(Offsets.OutsideField + AcreSizeAll)..], value);
}
public ushort MainFieldParamUniqueID
{
get => ReadUInt16LittleEndian(Data.Slice(Offsets.OutsideField + AcreSizeAll + 2));
set => WriteUInt16LittleEndian(Data.Slice(Offsets.OutsideField + AcreSizeAll + 2), value);
}
public uint EventPlazaLeftUpX
{
get => ReadUInt32LittleEndian(Data.Slice(Offsets.OutsideField + AcreSizeAll + 4));
set => WriteUInt32LittleEndian(Data.Slice(Offsets.OutsideField + AcreSizeAll + 4), value);
}
public uint EventPlazaLeftUpZ
{
get => ReadUInt32LittleEndian(Data.Slice(Offsets.OutsideField + AcreSizeAll + 8));
set => WriteUInt32LittleEndian(Data.Slice(Offsets.OutsideField + AcreSizeAll + 8), value);
}
public GSaveVisitorNpc Visitor
{
get => Data.Slice(Offsets.Visitor, GSaveVisitorNpc.SIZE).ToArray().ToClass<GSaveVisitorNpc>();
set => value.ToBytesClass().CopyTo(Data[Offsets.Visitor..]);
}
public GSaveFg SaveFg
{
get => Data.Slice(Offsets.SaveFg, GSaveFg.SIZE).ToArray().ToClass<GSaveFg>();
set => value.ToBytesClass().CopyTo(Data[Offsets.SaveFg..]);
}
public GSaveTime LastSaved => Data.Slice(Offsets.LastSavedTime, GSaveTime.SIZE).ToStructure<GSaveTime>();
public GSaveBulletinBoard Bulletin
{
get => Data.Slice(Offsets.BulletinBoard, GSaveBulletinBoard.SIZE).ToStructure<GSaveBulletinBoard>();
set => value.ToBytes().CopyTo(Data[Offsets.BulletinBoard..]);
}
}

View File

@ -1,172 +1,177 @@
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
{
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);
}
public string TownName
{
get => GetString(Offsets.PersonalId + 0x04, 10);
set => GetBytes(value, 10).CopyTo(Data, Offsets.PersonalId + 0x04);
}
public 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);
}
public string PlayerName
{
get => GetString(Offsets.PersonalId + 0x20, 10);
set => GetBytes(value, 10).CopyTo(Data, Offsets.PersonalId + 0x20);
}
public byte[] GetPlayerIdentity() => Data.Slice(Offsets.PersonalId + 0x1C, 4 + 20);
public EncryptedInt32 Wallet
{
get => EncryptedInt32.ReadVerify(Data, Offsets.Wallet);
set => value.Write(Data, Offsets.Wallet);
}
public EncryptedInt32 Bank
{
get => EncryptedInt32.ReadVerify(Data, Offsets.Bank);
set => value.Write(Data, Offsets.Bank);
}
public EncryptedInt32 NookMiles
{
get => EncryptedInt32.ReadVerify(Data, Offsets.NowPoint);
set => value.Write(Data, Offsets.NowPoint);
}
public EncryptedInt32 TotalNookMiles
{
get => EncryptedInt32.ReadVerify(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);
}
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));
}
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);
}
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));
}
public IReadOnlyList<Item> ItemChest
{
get => Item.GetArray(Data.Slice(Offsets.ItemChest, Offsets.ItemChestCount * Item.SIZE));
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));
}
public IReactionStore Reactions
{
get => Offsets.ReadReactions(Data);
set => Offsets.SetReactions(Data, value);
}
public AchievementList Achievements
{
get => Data.Slice(Offsets.CountAchievement, AchievementList.SIZE).ToStructure<AchievementList>();
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 short[] GetEventFlagsPlayer()
{
var result = new short[0xE00/2];
Buffer.BlockCopy(Data, Offsets.EventFlagsPlayer, result, 0, result.Length * sizeof(short));
return result;
}
public void SetEventFlagsPlayer(short[] value) => Buffer.BlockCopy(value, 0, Data, Offsets.EventFlagsPlayer, value.Length * sizeof(short));
public GSaveDateMD Birthday
{
get => Data.ToStructure<GSaveDateMD>(Offsets.Birthday, GSaveDateMD.SIZE);
set => value.ToBytes().CopyTo(Data, Offsets.Birthday);
}
#region Profile
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);
}
public GSaveDateMD ProfileBirthday
{
get => Data.ToStructure<GSaveDateMD>(Offsets.ProfileBirthday, GSaveDateMD.SIZE);
set => value.ToBytes().CopyTo(Data, Offsets.ProfileBirthday);
}
public ushort ProfileFruit
{
get => BitConverter.ToUInt16(Data, Offsets.ProfileFruit);
set => BitConverter.GetBytes(value).CopyTo(Data, Offsets.ProfileFruit);
}
public GSaveDate ProfileTimestamp
{
get => Data.ToStructure<GSaveDate>(Offsets.ProfileTimestamp, GSaveDate.SIZE);
set => value.ToBytes().CopyTo(Data, Offsets.ProfileTimestamp);
}
public bool ProfileIsMakeVillage
{
get => Data[Offsets.ProfileIsMakeVillage] != 0;
set => Data[Offsets.ProfileIsMakeVillage] = (byte)(value ? 1 : 0);
}
#endregion
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)..]);
}
public Span<byte> GetTownIdentity() => Data.Slice(Offsets.PersonalId + 0x00, 4 + 20);
public uint PlayerID
{
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)..]);
}
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..]);
}
public EncryptedInt32 Bank
{
get => EncryptedInt32.ReadVerify(Data, Offsets.Bank);
set => value.Write(Data[Offsets.Bank..]);
}
public EncryptedInt32 NookMiles
{
get => EncryptedInt32.ReadVerify(Data, Offsets.NowPoint);
set => value.Write(Data[Offsets.NowPoint..]);
}
public EncryptedInt32 TotalNookMiles
{
get => EncryptedInt32.ReadVerify(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..]);
}
public uint BagCount // Count of the Bag Slots that are available for use
{
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..]);
}
public uint PocketCount // Count of the Pocket Slots that are available for use
{
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..]);
}
public uint ItemChestCount // Count of the Item Chest Slots that are available for use
{
get => ReadUInt32LittleEndian(Data[(Offsets.ItemChest + (Offsets.ItemChestCount * Item.SIZE))..]);
set => WriteUInt32LittleEndian(Data[(Offsets.ItemChest + (Offsets.ItemChestCount * Item.SIZE))..], value);
}
public IReactionStore Reactions
{
get => Offsets.ReadReactions(Data);
set => Offsets.SetReactions(Data, value);
}
public AchievementList Achievements
{
get => Data.Slice(Offsets.CountAchievement, AchievementList.SIZE).ToStructure<AchievementList>();
set => value.ToBytes().CopyTo(Data[Offsets.CountAchievement..]);
}
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 slice = Data.Slice(Offsets.EventFlagsPlayer, 0xE00);
return MemoryMarshal.Cast<byte, short>(slice).ToArray();
}
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..]);
}
#region Profile
public byte[] GetPhotoData()
{
var offset = Offsets.ProfilePhoto;
// Expect jpeg marker
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..]);
}
public ushort 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..]);
}
public bool ProfileIsMakeVillage
{
get => Data[Offsets.ProfileIsMakeVillage] != 0;
set => Data[Offsets.ProfileIsMakeVillage] = (byte)(value ? 1 : 0);
}
#endregion
}

View File

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

View File

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

View File

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

View File

@ -1,18 +1,17 @@
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;
public WhereAreN(string folder) : base(folder, FileName) => Offsets = WhereAreNOffsets.GetOffsets(Info);
public EncryptedInt32 Poki
{
public const string FileName = "wherearen";
public readonly WhereAreNOffsets Offsets;
public WhereAreN(string folder) : base(folder, FileName) => Offsets = WhereAreNOffsets.GetOffsets(Info);
public EncryptedInt32 Poki
{
get => EncryptedInt32.ReadVerify(Data, Offsets.Poki);
set => value.Write(Data, Offsets.Poki);
}
get => EncryptedInt32.ReadVerify(Data, Offsets.Poki);
set => value.Write(Data[Offsets.Poki..]);
}
}
}

View File

@ -1,91 +1,94 @@
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
private readonly byte[] RawData;
private readonly byte[] RawHeader;
public Span<byte> Data => RawData;
public Span<byte> Header => RawHeader;
public readonly FileHeaderInfo Info;
public readonly string DataPath;
public readonly string HeaderPath;
public readonly string NameData;
public readonly string NameHeader;
public static bool Exists(string folder, string name)
{
public readonly byte[] Data;
public readonly byte[] Header;
public readonly FileHeaderInfo Info;
public readonly string DataPath;
public readonly string HeaderPath;
public readonly string NameData;
public readonly string NameHeader;
public static bool Exists(string folder, string name)
{
var NameData = $"{name}.dat";
var NameHeader = $"{name}Header.dat";
var hdr = Path.Combine(folder, NameHeader);
var dat = Path.Combine(folder, NameData);
return File.Exists(hdr) && File.Exists(dat);
}
protected EncryptedFilePair(string folder, string name)
{
NameData = $"{name}.dat";
NameHeader = $"{name}Header.dat";
var hdr = Path.Combine(folder, NameHeader);
var dat = Path.Combine(folder, NameData);
var hd = File.ReadAllBytes(hdr);
var md = File.ReadAllBytes(dat);
Encryption.Decrypt(hd, md);
Header = hd;
Data = md;
DataPath = dat;
HeaderPath = hdr;
Info = Header.Slice(0, FileHeaderInfo.SIZE).ToClass<FileHeaderInfo>();
}
public void Save(uint seed)
{
var encrypt = Encryption.Encrypt(Data, seed, Header);
File.WriteAllBytes(DataPath, encrypt.Data);
File.WriteAllBytes(HeaderPath, encrypt.Header);
}
/// <summary>
/// Updates all hashes of <see cref="Data"/>.
/// </summary>
public void Hash()
{
var ver = Info.GetKnownRevisionIndex();
var hash = RevisionChecker.HashInfo[ver];
var details = hash.GetFile(NameData);
if (details == null)
throw new ArgumentNullException(nameof(NameData));
foreach (var h in details.HashRegions)
Murmur3.UpdateMurmur32(Data, h.HashOffset, h.BeginOffset, (uint)h.Size);
}
public IEnumerable<FileHashRegion> InvalidHashes()
{
var ver = Info.GetKnownRevisionIndex();
var hash = RevisionChecker.HashInfo[ver];
var details = hash.GetFile(NameData);
if (details == null)
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);
if (current != saved)
yield return h;
}
}
protected string GetString(int offset, int maxLength) => StringUtil.GetString(Data, offset, maxLength);
protected static byte[] GetBytes(string value, int maxLength) => StringUtil.GetBytes(value, maxLength);
var NameData = $"{name}.dat";
var NameHeader = $"{name}Header.dat";
var hdr = Path.Combine(folder, NameHeader);
var dat = Path.Combine(folder, NameData);
return File.Exists(hdr) && File.Exists(dat);
}
protected EncryptedFilePair(string folder, string name)
{
NameData = $"{name}.dat";
NameHeader = $"{name}Header.dat";
var hdr = Path.Combine(folder, NameHeader);
var dat = Path.Combine(folder, NameData);
var hd = File.ReadAllBytes(hdr);
var md = File.ReadAllBytes(dat);
Encryption.Decrypt(hd, md);
RawHeader = hd;
RawData = md;
DataPath = dat;
HeaderPath = hdr;
Info = RawHeader[..FileHeaderInfo.SIZE].ToClass<FileHeaderInfo>();
}
public void Save(uint seed)
{
var encrypt = Encryption.Encrypt(RawData, seed, RawHeader);
File.WriteAllBytes(DataPath, encrypt.Data);
File.WriteAllBytes(HeaderPath, encrypt.Header);
}
/// <summary>
/// Updates all hashes of <see cref="Data"/>.
/// </summary>
public void Hash()
{
var ver = Info.GetKnownRevisionIndex();
var hash = RevisionChecker.HashInfo[ver];
var details = hash.GetFile(NameData);
if (details == null)
throw new ArgumentNullException(nameof(NameData));
foreach (var h in details.HashRegions)
Murmur3.UpdateMurmur32(Data.Slice(h.BeginOffset, h.Size), Data[h.HashOffset..]);
}
public IEnumerable<FileHashRegion> InvalidHashes()
{
var ver = Info.GetKnownRevisionIndex();
var hash = RevisionChecker.HashInfo[ver];
var details = hash.GetFile(NameData);
if (details == null)
throw new ArgumentNullException(nameof(NameData));
foreach (var h in details.HashRegions)
{
var current = Murmur3.GetMurmur3Hash(Data.Slice(h.BeginOffset, h.Size));
var saved = ReadUInt32LittleEndian(Data[h.HashOffset..]);
if (current != saved)
yield return h;
}
}
protected string GetString(int offset, int maxLength) => StringUtil.GetString(Data, offset, maxLength);
protected static byte[] GetBytes(string value, int maxLength) => StringUtil.GetBytes(value, maxLength);
}

View File

@ -1,21 +1,20 @@
using System.Runtime.InteropServices;
// ReSharper disable NonReadonlyMemberInGetHashCode
namespace NHSE.Core
{
/// <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;
namespace NHSE.Core;
[field: FieldOffset(0x00)] public uint Major { get; init; }
[field: FieldOffset(0x04)] public uint Minor { get; init; }
[field: FieldOffset(0x08)] public ushort Unk1 { get; init; }
[field: FieldOffset(0x0A)] public ushort HeaderRevision { get; init; }
[field: FieldOffset(0x0C)] public ushort Unk2 { get; init; }
[field: FieldOffset(0x0E)] public ushort SaveRevision { get; init; }
}
}
/// <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; }
[field: FieldOffset(0x04)] public uint Minor { get; init; }
[field: FieldOffset(0x08)] public ushort Unk1 { get; init; }
[field: FieldOffset(0x0A)] public ushort HeaderRevision { get; init; }
[field: FieldOffset(0x0C)] public ushort Unk2 { get; init; }
[field: FieldOffset(0x0E)] public ushort SaveRevision { get; init; }
}

View File

@ -1,102 +1,102 @@
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]}";
public HorizonSave(string folder)
{
public readonly MainSave Main;
public readonly Player[] Players;
public override string ToString() => $"{Players[0].Personal.TownName} - {Players[0]}";
Main = new MainSave(folder);
Players = Player.ReadMany(folder);
}
public HorizonSave(string folder)
/// <summary>
/// Saves the data using the provided crypto <see cref="seed"/>.
/// </summary>
/// <param name="seed">Seed to initialize the RNG with when encrypting the files.</param>
public void Save(uint seed)
{
Main.Hash();
Main.Save(seed);
foreach (var player in Players)
{
Main = new MainSave(folder);
Players = Player.ReadMany(folder);
}
/// <summary>
/// Saves the data using the provided crypto <see cref="seed"/>.
/// </summary>
/// <param name="seed">Seed to initialize the RNG with when encrypting the files.</param>
public void Save(uint seed)
{
Main.Hash();
Main.Save(seed);
foreach (var player in Players)
foreach (var pair in player)
{
foreach (var pair in player)
{
pair.Hash();
pair.Save(seed);
}
pair.Hash();
pair.Save(seed);
}
}
/// <summary>
/// Gets every <see cref="FileHashRegion"/> that is deemed invalid.
/// </summary>
/// <remarks>
/// Doesn't return any metadata about which file the hashes were bad for.
/// Just check what's returned with what's implemented; the offsets are unique enough.
/// </remarks>
public IEnumerable<FileHashRegion> GetInvalidHashes()
{
foreach (var hash in Main.InvalidHashes())
yield return hash;
foreach (var hash in Players.SelectMany(z => z).SelectMany(z => z.InvalidHashes()))
yield return hash;
}
public void ChangeIdentity(byte[] original, byte[] updated)
{
Main.Data.ReplaceOccurrences(original, updated);
foreach (var pair in Players.SelectMany(z => z))
pair.Data.ReplaceOccurrences(original, updated);
}
public bool ValidateSizes()
{
var info = Main.Info.GetKnownRevisionIndex();
if (info < 0)
return false;
var sizes = RevisionChecker.SizeInfo[info];
if (Main.Data.Length != sizes.Main)
return false;
// Each player present in the savedata must have been migrated to this revision.
foreach (var p in Players)
{
if (p.Personal.Data.Length != sizes.Personal)
return false;
if (p.Photo.Data.Length != sizes.PhotoStudioIsland)
return false;
if (p.PostBox.Data.Length != sizes.PostBox)
return false;
if (p.Profile.Data.Length != sizes.Profile)
return false;
if (p.WhereAreN is { } x && x.Data.Length != sizes.WhereAreN)
return false;
}
return true;
}
public string GetSaveTitle(string prefix)
{
var townName = Players[0].Personal.TownName;
var timestamp = Main.LastSaved.TimeStamp;
return $"{prefix} - {townName} @ {timestamp}";
}
public string GetBackupFolderTitle()
{
var townName = Players[0].Personal.TownName;
var timestamp = Main.LastSaved.TimeStamp.Replace(':', '.');
return StringUtil.CleanFileName($"{townName} - {timestamp}");
}
}
}
/// <summary>
/// Gets every <see cref="FileHashRegion"/> that is deemed invalid.
/// </summary>
/// <remarks>
/// Doesn't return any metadata about which file the hashes were bad for.
/// Just check what's returned with what's implemented; the offsets are unique enough.
/// </remarks>
public IEnumerable<FileHashRegion> GetInvalidHashes()
{
foreach (var hash in Main.InvalidHashes())
yield return hash;
foreach (var hash in Players.SelectMany(z => z).SelectMany(z => z.InvalidHashes()))
yield return hash;
}
public void ChangeIdentity(ReadOnlySpan<byte> original, ReadOnlySpan<byte> updated)
{
Main.Data.ReplaceOccurrences(original, updated);
foreach (var pair in Players.SelectMany(z => z))
pair.Data.ReplaceOccurrences(original, updated);
}
public bool ValidateSizes()
{
var info = Main.Info.GetKnownRevisionIndex();
if (info < 0)
return false;
var sizes = RevisionChecker.SizeInfo[info];
if (Main.Data.Length != sizes.Main)
return false;
// Each player present in the savedata must have been migrated to this revision.
foreach (var p in Players)
{
if (p.Personal.Data.Length != sizes.Personal)
return false;
if (p.Photo.Data.Length != sizes.PhotoStudioIsland)
return false;
if (p.PostBox.Data.Length != sizes.PostBox)
return false;
if (p.Profile.Data.Length != sizes.Profile)
return false;
if (p.WhereAreN is { } x && x.Data.Length != sizes.WhereAreN)
return false;
}
return true;
}
public string GetSaveTitle(string prefix)
{
var townName = Players[0].Personal.TownName;
var timestamp = Main.LastSaved.TimeStamp;
return $"{prefix} - {townName} @ {timestamp}";
}
public string GetBackupFolderTitle()
{
var townName = Players[0].Personal.TownName;
var timestamp = Main.LastSaved.TimeStamp.Replace(':', '.');
return StringUtil.CleanFileName($"{townName} - {timestamp}");
}
}

View File

@ -3,63 +3,62 @@
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>
{
public readonly Personal Personal;
public readonly PhotoStudioIsland Photo;
public readonly PostBox PostBox;
public readonly Profile Profile;
public readonly WhereAreN? WhereAreN;
/// <summary>
/// Stores references for all files in the Villager (<see cref="DirectoryName"/>) folder.
/// Directory Name where the player data was loaded from. Not the full path.
/// </summary>
public sealed class Player : IEnumerable<EncryptedFilePair>
public readonly string DirectoryName;
#region Override Implementations
public IEnumerator<EncryptedFilePair> GetEnumerator()
{
public readonly Personal Personal;
public readonly PhotoStudioIsland Photo;
public readonly PostBox PostBox;
public readonly Profile Profile;
public readonly WhereAreN? WhereAreN;
/// <summary>
/// Directory Name where the player data was loaded from. Not the full path.
/// </summary>
public readonly string DirectoryName;
#region Override Implementations
public IEnumerator<EncryptedFilePair> GetEnumerator()
{
IEnumerable<EncryptedFilePair> baseFiles = new EncryptedFilePair[] { Personal, Photo, PostBox, Profile };
if (WhereAreN is not null)
baseFiles = baseFiles.Concat(new EncryptedFilePair[] { WhereAreN });
return baseFiles.AsEnumerable().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public override string ToString() => Personal.PlayerName;
#endregion
/// <summary>
/// Imports Player data from the requested <see cref="folder"/>.
/// </summary>
/// <param name="folder">Folder that contains the Player Villager sub-folders.</param>
/// <returns>Player object array loaded from the <see cref="folder"/>.</returns>
public static Player[] ReadMany(string folder)
{
var dirs = Directory.GetDirectories(folder, "Villager*", SearchOption.TopDirectoryOnly);
var result = new Player[dirs.Length];
for (int i = 0; i < result.Length; i++)
result[i] = new Player(dirs[i]);
return result;
}
private Player(string folder)
{
DirectoryName = new DirectoryInfo(folder).Name;
Personal = new Personal(folder);
Photo = new PhotoStudioIsland(folder);
PostBox = new PostBox(folder);
Profile = new Profile(folder);
if (EncryptedFilePair.Exists(folder, WhereAreN.FileName))
WhereAreN = new WhereAreN(folder);
}
IEnumerable<EncryptedFilePair> baseFiles = [Personal, Photo, PostBox, Profile];
if (WhereAreN is not null)
baseFiles = baseFiles.Concat([WhereAreN]);
return baseFiles.AsEnumerable().GetEnumerator();
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public override string ToString() => Personal.PlayerName;
#endregion
/// <summary>
/// Imports Player data from the requested <see cref="folder"/>.
/// </summary>
/// <param name="folder">Folder that contains the Player Villager sub-folders.</param>
/// <returns>Player object array loaded from the <see cref="folder"/>.</returns>
public static Player[] ReadMany(string folder)
{
var dirs = Directory.GetDirectories(folder, "Villager*", SearchOption.TopDirectoryOnly);
var result = new Player[dirs.Length];
for (int i = 0; i < result.Length; i++)
result[i] = new Player(dirs[i]);
return result;
}
private Player(string folder)
{
DirectoryName = new DirectoryInfo(folder).Name;
Personal = new Personal(folder);
Photo = new PhotoStudioIsland(folder);
PostBox = new PostBox(folder);
Profile = new Profile(folder);
if (EncryptedFilePair.Exists(folder, WhereAreN.FileName))
WhereAreN = new WhereAreN(folder);
}
}

View File

@ -2,139 +2,141 @@
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.
/// Unique save file size list by patch.
/// </summary>
public static class RevisionChecker
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
new(REV_130_MAIN, REV_130_PERSONAL, REV_130_PHOTO, REV_130_POSTBOX, REV_130_PROFILE), // 1.3.0
new(REV_140_MAIN, REV_140_PERSONAL, REV_140_PHOTO, REV_140_POSTBOX, REV_140_PROFILE), // 1.4.0
new(REV_150_MAIN, REV_150_PERSONAL, REV_150_PHOTO, REV_150_POSTBOX, REV_150_PROFILE), // 1.5.0
new(REV_160_MAIN, REV_160_PERSONAL, REV_160_PHOTO, REV_160_POSTBOX, REV_160_PROFILE), // 1.6.0
new(REV_170_MAIN, REV_170_PERSONAL, REV_170_PHOTO, REV_170_POSTBOX, REV_170_PROFILE), // 1.7.0
new(REV_180_MAIN, REV_180_PERSONAL, REV_180_PHOTO, REV_180_POSTBOX, REV_180_PROFILE), // 1.8.0
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
];
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
new() { Major = 0x0006D, Minor = 0x00078, HeaderRevision = 0, Unk1 = 2, SaveRevision = 03, Unk2 = 2 }, // 1.1.2
new() { Major = 0x0006D, Minor = 0x00078, HeaderRevision = 0, Unk1 = 2, SaveRevision = 04, Unk2 = 2 }, // 1.1.3
new() { Major = 0x0006D, Minor = 0x00078, HeaderRevision = 0, Unk1 = 2, SaveRevision = 05, Unk2 = 2 }, // 1.1.4
new() { Major = 0x20006, Minor = 0x20008, HeaderRevision = 0, Unk1 = 2, SaveRevision = 06, Unk2 = 2 }, // 1.2.0
new() { Major = 0x20006, Minor = 0x20008, HeaderRevision = 0, Unk1 = 2, SaveRevision = 07, Unk2 = 2 }, // 1.2.1
new() { Major = 0x40002, Minor = 0x40008, HeaderRevision = 0, Unk1 = 2, SaveRevision = 08, Unk2 = 2 }, // 1.3.0
new() { Major = 0x40002, Minor = 0x40008, HeaderRevision = 0, Unk1 = 2, SaveRevision = 09, Unk2 = 2 }, // 1.3.1
new() { Major = 0x50001, Minor = 0x5000B, HeaderRevision = 0, Unk1 = 2, SaveRevision = 10, Unk2 = 2 }, // 1.4.0
new() { Major = 0x50001, Minor = 0x5000B, HeaderRevision = 0, Unk1 = 2, SaveRevision = 11, Unk2 = 2 }, // 1.4.1
new() { Major = 0x50001, Minor = 0x5000B, HeaderRevision = 0, Unk1 = 2, SaveRevision = 12, Unk2 = 2 }, // 1.4.2
new() { Major = 0x60001, Minor = 0x6000C, HeaderRevision = 0, Unk1 = 2, SaveRevision = 13, Unk2 = 2 }, // 1.5.0
new() { Major = 0x60001, Minor = 0x6000C, HeaderRevision = 0, Unk1 = 2, SaveRevision = 14, Unk2 = 2 }, // 1.5.1
new() { Major = 0x70001, Minor = 0x70006, HeaderRevision = 0, Unk1 = 2, SaveRevision = 15, Unk2 = 2 }, // 1.6.0
new() { Major = 0x74001, Minor = 0x74005, HeaderRevision = 0, Unk1 = 2, SaveRevision = 16, Unk2 = 2 }, // 1.7.0
new() { Major = 0x78001, Minor = 0x78001, HeaderRevision = 0, Unk1 = 2, SaveRevision = 17, Unk2 = 2 }, // 1.8.0
new() { Major = 0x7C001, Minor = 0x7C006, HeaderRevision = 0, Unk1 = 2, SaveRevision = 18, Unk2 = 2 }, // 1.9.0
new() { Major = 0x7D001, Minor = 0x7D004, HeaderRevision = 0, Unk1 = 2, SaveRevision = 19, Unk2 = 2 }, // 1.10.0
new() { Major = 0x7E001, Minor = 0x7E001, HeaderRevision = 0, Unk1 = 2, SaveRevision = 20, Unk2 = 2 }, // 1.11.0
new() { Major = 0x7E001, Minor = 0x7E001, HeaderRevision = 0, Unk1 = 2, SaveRevision = 21, Unk2 = 2 }, // 1.11.1
new() { Major = 0x80009, Minor = 0x80085, HeaderRevision = 0, Unk1 = 2, SaveRevision = 22, Unk2 = 2 }, // 2.0.0
new() { Major = 0x80009, Minor = 0x80085, HeaderRevision = 0, Unk1 = 2, SaveRevision = 23, Unk2 = 2 }, // 2.0.1
new() { Major = 0x80009, Minor = 0x80085, HeaderRevision = 0, Unk1 = 2, SaveRevision = 24, Unk2 = 2 }, // 2.0.2
new() { Major = 0x80009, Minor = 0x80085, HeaderRevision = 0, Unk1 = 2, SaveRevision = 25, Unk2 = 2 }, // 2.0.3
new() { Major = 0x80009, Minor = 0x80085, HeaderRevision = 0, Unk1 = 2, SaveRevision = 26, Unk2 = 2 }, // 2.0.4
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
];
public static readonly IReadOnlyList<SaveFileSizes> SizeInfo =
[
SizesByRevision[0], // 1.0.0
SizesByRevision[1], // 1.1.0
SizesByRevision[1], // 1.1.1
SizesByRevision[1], // 1.1.2
SizesByRevision[1], // 1.1.3
SizesByRevision[1], // 1.1.4
SizesByRevision[2], // 1.2.0
SizesByRevision[2], // 1.2.1
SizesByRevision[3], // 1.3.0
SizesByRevision[3], // 1.3.1
SizesByRevision[4], // 1.4.0
SizesByRevision[4], // 1.4.1
SizesByRevision[4], // 1.4.2
SizesByRevision[5], // 1.5.0
SizesByRevision[5], // 1.5.1
SizesByRevision[6], // 1.6.0
SizesByRevision[7], // 1.7.0
SizesByRevision[8], // 1.8.0
SizesByRevision[9], // 1.9.0
SizesByRevision[10], // 1.10.0
SizesByRevision[11], // 1.11.0
SizesByRevision[11], // 1.11.1
SizesByRevision[12], // 2.0.0
SizesByRevision[12], // 2.0.1
SizesByRevision[12], // 2.0.2
SizesByRevision[12], // 2.0.3
SizesByRevision[12], // 2.0.4
SizesByRevision[12], // 2.0.5
SizesByRevision[12], // 2.0.6
SizesByRevision[12], // 2.0.7
SizesByRevision[12] // 2.0.8
];
public static readonly IReadOnlyList<FileHashInfo> HashInfo =
[
REV_100, // 1.0.0
REV_110, // 1.1.0
REV_110, // 1.1.1
REV_110, // 1.1.2
REV_110, // 1.1.3
REV_110, // 1.1.4
REV_120, // 1.2.0
REV_120, // 1.2.1
REV_130, // 1.3.0
REV_130, // 1.3.1
REV_140, // 1.4.0
REV_140, // 1.4.1
REV_140, // 1.4.2
REV_150, // 1.5.0
REV_150, // 1.5.1
REV_160, // 1.6.0
REV_170, // 1.7.0
REV_180, // 1.8.0
REV_190, // 1.9.0
REV_1100, // 1.10.0
REV_1110, // 1.11.0
REV_1110, // 1.11.1
REV_200, // 2.0.0
REV_200, // 2.0.1
REV_200, // 2.0.2
REV_200, // 2.0.3
REV_200, // 2.0.4
REV_200, // 2.0.5
REV_200, // 2.0.6
REV_200, // 2.0.7
REV_200 // 2.0.8
];
extension(FileHeaderInfo info)
{
/// <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
new(REV_130_MAIN, REV_130_PERSONAL, REV_130_PHOTO, REV_130_POSTBOX, REV_130_PROFILE), // 1.3.0
new(REV_140_MAIN, REV_140_PERSONAL, REV_140_PHOTO, REV_140_POSTBOX, REV_140_PROFILE), // 1.4.0
new(REV_150_MAIN, REV_150_PERSONAL, REV_150_PHOTO, REV_150_POSTBOX, REV_150_PROFILE), // 1.5.0
new(REV_160_MAIN, REV_160_PERSONAL, REV_160_PHOTO, REV_160_POSTBOX, REV_160_PROFILE), // 1.6.0
new(REV_170_MAIN, REV_170_PERSONAL, REV_170_PHOTO, REV_170_POSTBOX, REV_170_PROFILE), // 1.7.0
new(REV_180_MAIN, REV_180_PERSONAL, REV_180_PHOTO, REV_180_POSTBOX, REV_180_PROFILE), // 1.8.0
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
};
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
new() { Major = 0x0006D, Minor = 0x00078, HeaderRevision = 0, Unk1 = 2, SaveRevision = 03, Unk2 = 2 }, // 1.1.2
new() { Major = 0x0006D, Minor = 0x00078, HeaderRevision = 0, Unk1 = 2, SaveRevision = 04, Unk2 = 2 }, // 1.1.3
new() { Major = 0x0006D, Minor = 0x00078, HeaderRevision = 0, Unk1 = 2, SaveRevision = 05, Unk2 = 2 }, // 1.1.4
new() { Major = 0x20006, Minor = 0x20008, HeaderRevision = 0, Unk1 = 2, SaveRevision = 06, Unk2 = 2 }, // 1.2.0
new() { Major = 0x20006, Minor = 0x20008, HeaderRevision = 0, Unk1 = 2, SaveRevision = 07, Unk2 = 2 }, // 1.2.1
new() { Major = 0x40002, Minor = 0x40008, HeaderRevision = 0, Unk1 = 2, SaveRevision = 08, Unk2 = 2 }, // 1.3.0
new() { Major = 0x40002, Minor = 0x40008, HeaderRevision = 0, Unk1 = 2, SaveRevision = 09, Unk2 = 2 }, // 1.3.1
new() { Major = 0x50001, Minor = 0x5000B, HeaderRevision = 0, Unk1 = 2, SaveRevision = 10, Unk2 = 2 }, // 1.4.0
new() { Major = 0x50001, Minor = 0x5000B, HeaderRevision = 0, Unk1 = 2, SaveRevision = 11, Unk2 = 2 }, // 1.4.1
new() { Major = 0x50001, Minor = 0x5000B, HeaderRevision = 0, Unk1 = 2, SaveRevision = 12, Unk2 = 2 }, // 1.4.2
new() { Major = 0x60001, Minor = 0x6000C, HeaderRevision = 0, Unk1 = 2, SaveRevision = 13, Unk2 = 2 }, // 1.5.0
new() { Major = 0x60001, Minor = 0x6000C, HeaderRevision = 0, Unk1 = 2, SaveRevision = 14, Unk2 = 2 }, // 1.5.1
new() { Major = 0x70001, Minor = 0x70006, HeaderRevision = 0, Unk1 = 2, SaveRevision = 15, Unk2 = 2 }, // 1.6.0
new() { Major = 0x74001, Minor = 0x74005, HeaderRevision = 0, Unk1 = 2, SaveRevision = 16, Unk2 = 2 }, // 1.7.0
new() { Major = 0x78001, Minor = 0x78001, HeaderRevision = 0, Unk1 = 2, SaveRevision = 17, Unk2 = 2 }, // 1.8.0
new() { Major = 0x7C001, Minor = 0x7C006, HeaderRevision = 0, Unk1 = 2, SaveRevision = 18, Unk2 = 2 }, // 1.9.0
new() { Major = 0x7D001, Minor = 0x7D004, HeaderRevision = 0, Unk1 = 2, SaveRevision = 19, Unk2 = 2 }, // 1.10.0
new() { Major = 0x7E001, Minor = 0x7E001, HeaderRevision = 0, Unk1 = 2, SaveRevision = 20, Unk2 = 2 }, // 1.11.0
new() { Major = 0x7E001, Minor = 0x7E001, HeaderRevision = 0, Unk1 = 2, SaveRevision = 21, Unk2 = 2 }, // 1.11.1
new() { Major = 0x80009, Minor = 0x80085, HeaderRevision = 0, Unk1 = 2, SaveRevision = 22, Unk2 = 2 }, // 2.0.0
new() { Major = 0x80009, Minor = 0x80085, HeaderRevision = 0, Unk1 = 2, SaveRevision = 23, Unk2 = 2 }, // 2.0.1
new() { Major = 0x80009, Minor = 0x80085, HeaderRevision = 0, Unk1 = 2, SaveRevision = 24, Unk2 = 2 }, // 2.0.2
new() { Major = 0x80009, Minor = 0x80085, HeaderRevision = 0, Unk1 = 2, SaveRevision = 25, Unk2 = 2 }, // 2.0.3
new() { Major = 0x80009, Minor = 0x80085, HeaderRevision = 0, Unk1 = 2, SaveRevision = 26, Unk2 = 2 }, // 2.0.4
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
};
public static readonly IReadOnlyList<SaveFileSizes> SizeInfo = new[]
{
SizesByRevision[0], // 1.0.0
SizesByRevision[1], // 1.1.0
SizesByRevision[1], // 1.1.1
SizesByRevision[1], // 1.1.2
SizesByRevision[1], // 1.1.3
SizesByRevision[1], // 1.1.4
SizesByRevision[2], // 1.2.0
SizesByRevision[2], // 1.2.1
SizesByRevision[3], // 1.3.0
SizesByRevision[3], // 1.3.1
SizesByRevision[4], // 1.4.0
SizesByRevision[4], // 1.4.1
SizesByRevision[4], // 1.4.2
SizesByRevision[5], // 1.5.0
SizesByRevision[5], // 1.5.1
SizesByRevision[6], // 1.6.0
SizesByRevision[7], // 1.7.0
SizesByRevision[8], // 1.8.0
SizesByRevision[9], // 1.9.0
SizesByRevision[10], // 1.10.0
SizesByRevision[11], // 1.11.0
SizesByRevision[11], // 1.11.1
SizesByRevision[12], // 2.0.0
SizesByRevision[12], // 2.0.1
SizesByRevision[12], // 2.0.2
SizesByRevision[12], // 2.0.3
SizesByRevision[12], // 2.0.4
SizesByRevision[12], // 2.0.5
SizesByRevision[12], // 2.0.6
SizesByRevision[12], // 2.0.7
SizesByRevision[12], // 2.0.8
};
public static readonly IReadOnlyList<FileHashInfo> HashInfo = new[]
{
REV_100, // 1.0.0
REV_110, // 1.1.0
REV_110, // 1.1.1
REV_110, // 1.1.2
REV_110, // 1.1.3
REV_110, // 1.1.4
REV_120, // 1.2.0
REV_120, // 1.2.1
REV_130, // 1.3.0
REV_130, // 1.3.1
REV_140, // 1.4.0
REV_140, // 1.4.1
REV_140, // 1.4.2
REV_150, // 1.5.0
REV_150, // 1.5.1
REV_160, // 1.6.0
REV_170, // 1.7.0
REV_180, // 1.8.0
REV_190, // 1.9.0
REV_1100, // 1.10.0
REV_1110, // 1.11.0
REV_1110, // 1.11.1
REV_200, // 2.0.0
REV_200, // 2.0.1
REV_200, // 2.0.2
REV_200, // 2.0.3
REV_200, // 2.0.4
REV_200, // 2.0.5
REV_200, // 2.0.6
REV_200, // 2.0.7
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));
public bool IsRevisionKnown() => info.GetKnownRevisionIndex() >= 0;
public int GetKnownRevisionIndex() => Array.FindIndex(RevisionInfo, z => z.Equals(info));
}
}

View File

@ -1,25 +1,24 @@
namespace NHSE.Core
{
/// <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;
public readonly uint PostBox;
public readonly uint Profile;
public readonly uint WhereAreN;
namespace NHSE.Core;
public SaveFileSizes(uint main, uint personal, uint photo, uint postbox, uint profile, uint wherearen = 0)
{
Main = main;
Personal = personal;
PhotoStudioIsland = photo;
PostBox = postbox;
Profile = profile;
WhereAreN = wherearen;
}
/// <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;
public readonly uint PostBox;
public readonly uint Profile;
public readonly uint WhereAreN;
public SaveFileSizes(uint main, uint personal, uint photo, uint postbox, uint profile, uint wherearen = 0)
{
Main = main;
Personal = personal;
PhotoStudioIsland = photo;
PostBox = postbox;
Profile = profile;
WhereAreN = wherearen;
}
}
}

View File

@ -1,209 +1,208 @@
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;
protected const int PatternCount1 = 50;
protected const int PatternCount2 = 100;
public const int PatternTailorCount = 8;
public const int BuildingCount = 46;
public const int RecycleBinCount = 40;
public abstract int Animal { get; }
public abstract int LandMyDesign { get; }
public abstract int PatternsPRO { get; }
public abstract int PatternFlag { get; }
public abstract int PatternTailor { get; }
public abstract int PatternsEditFlagStart { get; }
public abstract int PatternsProEditFlagStart { get; }
public abstract int WeatherArea { get; }
public abstract int WeatherRandSeed { get; }
public abstract int MainFieldStructure { get; }
public abstract int EventFlagLand { get; }
public abstract int FieldItem { get; }
public abstract int LandMakingMap { get; }
public abstract int OutsideField { get; }
public abstract int MyDesignMap { get; }
public abstract int PlayerHouseList { get; }
public abstract int NpcHouseList { get; }
public abstract int ShopKabu { get; }
public abstract int Museum { get; }
public abstract int Visitor { get; }
public abstract int SaveFg { get; }
public abstract int BulletinBoard { get; }
public abstract int AirportThemeColor { get; }
public abstract int LostItemBox { get; }
public abstract int LastSavedTime { get; }
public abstract int VillagerSize { get; }
public abstract int VillagerHouseSize { get; }
public abstract int PlayerHouseSize { get; }
public abstract int PlayerRoomSize { get; }
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)
{
public const int PlayerCount = 8;
public const int VillagerCount = 10;
public virtual int PatternCount => PatternCount1;
protected const int PatternCount1 = 50;
protected const int PatternCount2 = 100;
public const int PatternTailorCount = 8;
public const int BuildingCount = 46;
public const int RecycleBinCount = 40;
public abstract int Animal { get; }
public abstract int LandMyDesign { get; }
public abstract int PatternsPRO { get; }
public abstract int PatternFlag { get; }
public abstract int PatternTailor { get; }
public abstract int PatternsEditFlagStart { get; }
public abstract int PatternsProEditFlagStart { get; }
public abstract int WeatherArea { get; }
public abstract int WeatherRandSeed { get; }
public abstract int MainFieldStructure { get; }
public abstract int EventFlagLand { get; }
public abstract int FieldItem { get; }
public abstract int LandMakingMap { get; }
public abstract int OutsideField { get; }
public abstract int MyDesignMap { get; }
public abstract int PlayerHouseList { get; }
public abstract int NpcHouseList { get; }
public abstract int ShopKabu { get; }
public abstract int Museum { get; }
public abstract int Visitor { get; }
public abstract int SaveFg { get; }
public abstract int BulletinBoard { get; }
public abstract int AirportThemeColor { get; }
public abstract int LostItemBox { get; }
public abstract int LastSavedTime { get; }
public abstract int VillagerSize { get; }
public abstract int VillagerHouseSize { get; }
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 static MainSaveOffsets GetOffsets(FileHeaderInfo Info)
var rev = Info.GetKnownRevisionIndex();
return rev switch
{
var rev = Info.GetKnownRevisionIndex();
return rev switch
{
0 => new MainSaveOffsets10(),
1 => new MainSaveOffsets11(),
2 => new MainSaveOffsets11(),
3 => new MainSaveOffsets11(),
4 => new MainSaveOffsets11(),
5 => new MainSaveOffsets11(),
6 => new MainSaveOffsets12(),
7 => new MainSaveOffsets12(),
8 => new MainSaveOffsets13(),
9 => new MainSaveOffsets13(),
10 => new MainSaveOffsets14(),
11 => new MainSaveOffsets14(),
12 => new MainSaveOffsets14(),
13 => new MainSaveOffsets15(),
14 => new MainSaveOffsets15(),
15 => new MainSaveOffsets16(),
16 => new MainSaveOffsets17(),
17 => new MainSaveOffsets18(),
18 => new MainSaveOffsets19(),
19 => new MainSaveOffsets110(),
20 => new MainSaveOffsets111(),
21 => new MainSaveOffsets111(),
22 => new MainSaveOffsets20(),
23 => new MainSaveOffsets20(),
24 => new MainSaveOffsets20(),
25 => new MainSaveOffsets20(),
26 => new MainSaveOffsets20(),
27 => new MainSaveOffsets20(),
28 => new MainSaveOffsets20(),
29 => new MainSaveOffsets20(),
30 => new MainSaveOffsets20(),
_ => throw new IndexOutOfRangeException("Unknown revision!" + Environment.NewLine + Info),
};
}
public DesignPattern ReadPattern(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)
{
var v = data.Slice(offset, DesignPattern.SIZE);
return new DesignPattern(v);
}
public void WritePattern(DesignPattern p, byte[] data, int index, byte[] playerID, 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
}
public DesignPatternPRO ReadPatternPRO(byte[] data, int index)
{
if ((uint)index >= PatternCount)
throw new ArgumentOutOfRangeException(nameof(index));
var ofs = PatternsPRO + (index * DesignPatternPRO.SIZE);
return ReadPatternPROAtOffset(data, ofs);
}
public static DesignPatternPRO ReadPatternPROAtOffset(byte[] data, int ofs)
{
var v = data.Slice(ofs, DesignPatternPRO.SIZE);
return new DesignPatternPRO(v);
}
public void WritePatternPRO(DesignPatternPRO p, byte[] data, int index, byte[] playerID, 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);
}
public IVillager ReadVillager(byte[] data, int index)
{
if ((uint)index >= VillagerCount)
throw new ArgumentOutOfRangeException(nameof(index));
var size = VillagerSize;
var v = data.Slice(Animal + (index * size), size);
return ReadVillager(v);
}
public void WriteVillager(IVillager v, byte[] data, int index)
{
if ((uint)index >= VillagerCount)
throw new ArgumentOutOfRangeException(nameof(index));
var size = VillagerSize;
v.Write().CopyTo(data, Animal + (index * size));
}
public IVillagerHouse ReadVillagerHouse(byte[] data, int index)
{
if ((uint)index >= VillagerCount)
throw new ArgumentOutOfRangeException(nameof(index));
var size = VillagerHouseSize;
var v = data.Slice(NpcHouseList + (index * size), size);
return ReadVillagerHouse(v);
}
public void WriteVillagerHouse(IVillagerHouse v, byte[] data, int index)
{
if ((uint)index >= VillagerCount)
throw new ArgumentOutOfRangeException(nameof(index));
var size = VillagerHouseSize;
v.Write().CopyTo(data, NpcHouseList + (index * size));
}
public IPlayerHouse ReadPlayerHouse(byte[] data, int index)
{
if ((uint)index >= PlayerCount)
throw new ArgumentOutOfRangeException(nameof(index));
var size = PlayerHouseSize;
var v = data.Slice(PlayerHouseList + (index * size), size);
return ReadPlayerHouse(v);
}
public void WritePlayerHouse(IPlayerHouse v, byte[] data, int index)
{
if ((uint)index >= PlayerCount)
throw new ArgumentOutOfRangeException(nameof(index));
var size = PlayerHouseSize;
v.Write().CopyTo(data, PlayerHouseList + (index * size));
}
0 => new MainSaveOffsets10(),
1 => new MainSaveOffsets11(),
2 => new MainSaveOffsets11(),
3 => new MainSaveOffsets11(),
4 => new MainSaveOffsets11(),
5 => new MainSaveOffsets11(),
6 => new MainSaveOffsets12(),
7 => new MainSaveOffsets12(),
8 => new MainSaveOffsets13(),
9 => new MainSaveOffsets13(),
10 => new MainSaveOffsets14(),
11 => new MainSaveOffsets14(),
12 => new MainSaveOffsets14(),
13 => new MainSaveOffsets15(),
14 => new MainSaveOffsets15(),
15 => new MainSaveOffsets16(),
16 => new MainSaveOffsets17(),
17 => new MainSaveOffsets18(),
18 => new MainSaveOffsets19(),
19 => new MainSaveOffsets110(),
20 => new MainSaveOffsets111(),
21 => new MainSaveOffsets111(),
22 => new MainSaveOffsets20(),
23 => new MainSaveOffsets20(),
24 => new MainSaveOffsets20(),
25 => new MainSaveOffsets20(),
26 => new MainSaveOffsets20(),
27 => new MainSaveOffsets20(),
28 => new MainSaveOffsets20(),
29 => new MainSaveOffsets20(),
30 => new MainSaveOffsets20(),
_ => throw new IndexOutOfRangeException("Unknown revision!" + Environment.NewLine + Info),
};
}
}
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(ReadOnlySpan<byte> data, int offset)
{
var v = data.Slice(offset, DesignPattern.SIZE).ToArray();
return new DesignPattern(v);
}
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
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(ReadOnlySpan<byte> data, int index)
{
if ((uint)index >= PatternCount)
throw new ArgumentOutOfRangeException(nameof(index));
var ofs = PatternsPRO + (index * DesignPatternPRO.SIZE);
return ReadPatternPROAtOffset(data, ofs);
}
public static DesignPatternPRO ReadPatternPROAtOffset(ReadOnlySpan<byte> data, int ofs)
{
var v = data.Slice(ofs, DesignPatternPRO.SIZE).ToArray();
return new DesignPatternPRO(v);
}
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
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(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).ToArray();
return ReadVillager(v);
}
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))..]);
}
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).ToArray();
return ReadVillagerHouse(v);
}
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))..]);
}
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).ToArray();
return ReadPlayerHouse(v);
}
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))..]);
}
}

View File

@ -1,65 +1,66 @@
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;
#region GSaveLand
public const int GSaveLandStart = 0x108;
public override int Animal => GSaveLandStart + 0x08;
public override int LandMyDesign => GSaveLandStart + 0x1D71E8;
public override int PatternsPRO => LandMyDesign + (PatternCount * DesignPattern.SIZE);
public override int PatternFlag => PatternsPRO + (PatternCount * DesignPatternPRO.SIZE);
public override int PatternTailor => PatternFlag + DesignPattern.SIZE;
public override int LandMyDesign => GSaveLandStart + 0x1D71E8;
public override int PatternsPRO => LandMyDesign + (PatternCount * DesignPattern.SIZE);
public override int PatternFlag => PatternsPRO + (PatternCount * DesignPatternPRO.SIZE);
public override int PatternTailor => PatternFlag + DesignPattern.SIZE;
public override int PatternsEditFlagStart => GSaveLandStart + 0x8BE150; // x100, HasPlayerEdited?
public override int PatternsProEditFlagStart => PatternsEditFlagStart + 0x64; // x100, HasPlayerEdited?
public override int PatternsEditFlagStart => GSaveLandStart + 0x8BE150; // x100, HasPlayerEdited?
public override int PatternsProEditFlagStart => PatternsEditFlagStart + 0x64; // x100, HasPlayerEdited?
public const int GSaveWeather = GSaveLandStart + 0x1D6FB0;
public override int WeatherArea => GSaveWeather + 0x14; // Hemisphere
public override int WeatherRandSeed => GSaveWeather + 0x18;
public const int GSaveWeather = GSaveLandStart + 0x1D6FB0;
public override int WeatherArea => GSaveWeather + 0x14; // Hemisphere
public override int WeatherRandSeed => GSaveWeather + 0x18;
public override int EventFlagLand => GSaveLandStart + 0x200FF4;
public override int EventFlagLand => GSaveLandStart + 0x200FF4;
// GSaveMainField
public const int GSaveMainFieldStart = GSaveLandStart + 0x2017F4;
public override int FieldItem => GSaveMainFieldStart + 0x00000;
public override int LandMakingMap => GSaveMainFieldStart + 0xAAA00;
public override int MainFieldStructure => GSaveMainFieldStart + 0xCF600;
public override int OutsideField => GSaveMainFieldStart + 0xCF998;
public override int MyDesignMap => GSaveMainFieldStart + 0xCFA34;
// GSaveMainField
public const int GSaveMainFieldStart = GSaveLandStart + 0x2017F4;
public override int FieldItem => GSaveMainFieldStart + 0x00000;
public override int LandMakingMap => GSaveMainFieldStart + 0xAAA00;
public override int MainFieldStructure => GSaveMainFieldStart + 0xCF600;
public override int OutsideField => GSaveMainFieldStart + 0xCF998;
public override int MyDesignMap => GSaveMainFieldStart + 0xCFA34;
public override int PlayerHouseList => GSaveLandStart + 0x2DBAA0;
public override int NpcHouseList => GSaveLandStart + 0x40DAA0;
public override int PlayerHouseList => GSaveLandStart + 0x2DBAA0;
public override int NpcHouseList => GSaveLandStart + 0x40DAA0;
public const int GSaveShop = GSaveLandStart + 0x40ECE8;
public override int ShopKabu => GSaveShop + 0x2AD0; // part of shop
public override int Museum => GSaveLandStart + 0x411A80;
public override int Visitor => GSaveLandStart + 0x414E84;
public override int SaveFg => GSaveLandStart + 0x4150B4;
public override int BulletinBoard => GSaveLandStart + 0x4159F8;
public override int AirportThemeColor => GSaveLandStart + 0x4F6600;
#endregion
public const int GSaveShop = GSaveLandStart + 0x40ECE8;
public override int ShopKabu => GSaveShop + 0x2AD0; // part of shop
public override int Museum => GSaveLandStart + 0x411A80;
public override int Visitor => GSaveLandStart + 0x414E84;
public override int SaveFg => GSaveLandStart + 0x4150B4;
public override int BulletinBoard => GSaveLandStart + 0x4159F8;
public override int AirportThemeColor => GSaveLandStart + 0x4F6600;
#endregion
#region GSaveLandOther
public const int GSaveLandOtherStart = 0x4FA1E0;
#region GSaveLandOther
public const int GSaveLandOtherStart = 0x4FA1E0;
public override int LostItemBox => GSaveLandOtherStart + 0x5C1E20;
public override int LastSavedTime => GSaveLandOtherStart + 0x5C6748;
#endregion
public override int LostItemBox => GSaveLandOtherStart + 0x5C1E20;
public override int LastSavedTime => GSaveLandOtherStart + 0x5C6748;
#endregion
public override int VillagerSize => Villager1.SIZE;
public override IVillager ReadVillager(byte[] data) => new Villager1(data);
public override int VillagerSize => Villager1.SIZE;
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 int VillagerHouseSize => VillagerHouse1.SIZE;
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 int PlayerHouseSize => PlayerHouse1.SIZE;
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 int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(Memory<byte> data) => new PlayerRoom1(data);
}

View File

@ -1,64 +1,65 @@
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;
#region GSaveLand
public const int GSaveLandStart = 0x110;
public override int Animal => GSaveLandStart + 0x10;
public override int LandMyDesign => GSaveLandStart + 0x1D7200;
public override int PatternsPRO => LandMyDesign + (PatternCount * DesignPattern.SIZE);
public override int PatternFlag => PatternsPRO + (PatternCount * DesignPatternPRO.SIZE);
public override int PatternTailor => PatternFlag + DesignPattern.SIZE;
public override int LandMyDesign => GSaveLandStart + 0x1D7200;
public override int PatternsPRO => LandMyDesign + (PatternCount * DesignPattern.SIZE);
public override int PatternFlag => PatternsPRO + (PatternCount * DesignPatternPRO.SIZE);
public override int PatternTailor => PatternFlag + DesignPattern.SIZE;
public override int PatternsEditFlagStart => GSaveLandStart + 0x8BE150; // x100, HasPlayerEdited?
public override int PatternsProEditFlagStart => PatternsEditFlagStart + 0x64; // x100, HasPlayerEdited?
public override int PatternsEditFlagStart => GSaveLandStart + 0x8BE150; // x100, HasPlayerEdited?
public override int PatternsProEditFlagStart => PatternsEditFlagStart + 0x64; // x100, HasPlayerEdited?
public const int GSaveWeather = GSaveLandStart + 0x1D6F98;
public override int WeatherArea => GSaveWeather + 0x14; // Hemisphere
public override int WeatherRandSeed => GSaveWeather + 0x18;
public const int GSaveWeather = GSaveLandStart + 0x1D6F98;
public override int WeatherArea => GSaveWeather + 0x14; // Hemisphere
public override int WeatherRandSeed => GSaveWeather + 0x18;
public override int EventFlagLand => GSaveLandStart + 0x20100C;
public override int EventFlagLand => GSaveLandStart + 0x20100C;
// GSaveMainField
public const int GSaveMainFieldStart = GSaveLandStart + 0x20180C;
public override int FieldItem => GSaveMainFieldStart + 0x00000;
public override int LandMakingMap => GSaveMainFieldStart + 0xAAA00;
public override int MainFieldStructure => GSaveMainFieldStart + 0xCF600;
public override int OutsideField => GSaveMainFieldStart + 0xCF998;
public override int MyDesignMap => GSaveMainFieldStart + 0xCFA34;
// GSaveMainField
public const int GSaveMainFieldStart = GSaveLandStart + 0x20180C;
public override int FieldItem => GSaveMainFieldStart + 0x00000;
public override int LandMakingMap => GSaveMainFieldStart + 0xAAA00;
public override int MainFieldStructure => GSaveMainFieldStart + 0xCF600;
public override int OutsideField => GSaveMainFieldStart + 0xCF998;
public override int MyDesignMap => GSaveMainFieldStart + 0xCFA34;
public override int PlayerHouseList => GSaveLandStart + 0x2DC238;
public override int NpcHouseList => GSaveLandStart + 0x40E238;
public override int PlayerHouseList => GSaveLandStart + 0x2DC238;
public override int NpcHouseList => GSaveLandStart + 0x40E238;
public const int GSaveShop = GSaveLandStart + 0x40F480;
public override int ShopKabu => GSaveShop + 0x2AD0; // part of shop
public override int Museum => GSaveLandStart + 0x412218;
public override int Visitor => GSaveLandStart + 0x41561C;
public override int SaveFg => GSaveLandStart + 0x41584C;
public override int BulletinBoard => GSaveLandStart + 0x416190;
public override int AirportThemeColor => GSaveLandStart + 0x4F6D98;
#endregion
public const int GSaveShop = GSaveLandStart + 0x40F480;
public override int ShopKabu => GSaveShop + 0x2AD0; // part of shop
public override int Museum => GSaveLandStart + 0x412218;
public override int Visitor => GSaveLandStart + 0x41561C;
public override int SaveFg => GSaveLandStart + 0x41584C;
public override int BulletinBoard => GSaveLandStart + 0x416190;
public override int AirportThemeColor => GSaveLandStart + 0x4F6D98;
#endregion
#region GSaveLandOther
public const int GSaveLandOtherStart = 0x4FAA80;
#region GSaveLandOther
public const int GSaveLandOtherStart = 0x4FAA80;
public override int LostItemBox => GSaveLandOtherStart + 0x5C33F0;
public override int LastSavedTime => GSaveLandOtherStart + 0x5C7D48;
#endregion
public override int LostItemBox => GSaveLandOtherStart + 0x5C33F0;
public override int LastSavedTime => GSaveLandOtherStart + 0x5C7D48;
#endregion
public override int VillagerSize => Villager1.SIZE;
public override IVillager ReadVillager(byte[] data) => new Villager1(data);
public override int VillagerSize => Villager1.SIZE;
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 int VillagerHouseSize => VillagerHouse1.SIZE;
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 int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data);
}
}
public override int PlayerHouseSize => PlayerHouse1.SIZE;
public override IPlayerHouse ReadPlayerHouse(Memory<byte> data) => new PlayerHouse1(data);
public override int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(Memory<byte> data) => new PlayerRoom1(data);
}

View File

@ -1,66 +1,67 @@
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;
public override int PatternCount => PatternCount2;
#region GSaveLand
public const int GSaveLandStart = 0x110;
public override int Animal => GSaveLandStart + 0x10;
#region GSaveLand
public const int GSaveLandStart = 0x110;
public override int Animal => GSaveLandStart + 0x10;
public override int LandMyDesign => GSaveLandStart + 0x1e2610;
public override int PatternsPRO => LandMyDesign + (PatternCount2 * DesignPattern.SIZE);
public override int PatternFlag => PatternsPRO + (PatternCount2 * DesignPatternPRO.SIZE);
public override int PatternTailor => PatternFlag + DesignPattern.SIZE;
public override int LandMyDesign => GSaveLandStart + 0x1e2610;
public override int PatternsPRO => LandMyDesign + (PatternCount2 * DesignPattern.SIZE);
public override int PatternFlag => PatternsPRO + (PatternCount2 * DesignPatternPRO.SIZE);
public override int PatternTailor => PatternFlag + DesignPattern.SIZE;
public override int PatternsEditFlagStart => GSaveLandStart + 0x8BE150; // x100, HasPlayerEdited?
public override int PatternsProEditFlagStart => PatternsEditFlagStart + 0x64; // x100, HasPlayerEdited?
public override int PatternsEditFlagStart => GSaveLandStart + 0x8BE150; // x100, HasPlayerEdited?
public override int PatternsProEditFlagStart => PatternsEditFlagStart + 0x64; // x100, HasPlayerEdited?
public const int GSaveWeather = GSaveLandStart + 0x1e23c0;
public override int WeatherArea => GSaveWeather + 0x14; // Hemisphere
public override int WeatherRandSeed => GSaveWeather + 0x18;
public const int GSaveWeather = GSaveLandStart + 0x1e23c0;
public override int WeatherArea => GSaveWeather + 0x14; // Hemisphere
public override int WeatherRandSeed => GSaveWeather + 0x18;
public override int EventFlagLand => GSaveLandStart + 0x22d9b8;
public override int EventFlagLand => GSaveLandStart + 0x22d9b8;
// GSaveMainField
public const int GSaveMainFieldStart = GSaveLandStart + 0x22e1b8;
public override int FieldItem => GSaveMainFieldStart + 0x00000;
public override int LandMakingMap => GSaveMainFieldStart + 0xAAA00;
public override int MainFieldStructure => GSaveMainFieldStart + 0xCF600;
public override int OutsideField => GSaveMainFieldStart + 0xCF998;
public override int MyDesignMap => GSaveMainFieldStart + 0xCFA34;
// GSaveMainField
public const int GSaveMainFieldStart = GSaveLandStart + 0x22e1b8;
public override int FieldItem => GSaveMainFieldStart + 0x00000;
public override int LandMakingMap => GSaveMainFieldStart + 0xAAA00;
public override int MainFieldStructure => GSaveMainFieldStart + 0xCF600;
public override int OutsideField => GSaveMainFieldStart + 0xCF998;
public override int MyDesignMap => GSaveMainFieldStart + 0xCFA34;
public override int PlayerHouseList => GSaveLandStart + 0x308be4;
public override int NpcHouseList => GSaveLandStart + 0x43abe4;
public override int PlayerHouseList => GSaveLandStart + 0x308be4;
public override int NpcHouseList => GSaveLandStart + 0x43abe4;
public const int GSaveShop = GSaveLandStart + 0x43be2c;
public override int ShopKabu => GSaveShop + 0x2d40; // part of shop; tailor & zakka increased size
public override int Museum => GSaveLandStart + 0x43f1e0;
public override int Visitor => GSaveLandStart + 0x4425e4;
public override int SaveFg => GSaveLandStart + 0x442814;
public override int BulletinBoard => GSaveLandStart + 0x443158;
public override int AirportThemeColor => GSaveLandStart + 0x523d60;
#endregion
public const int GSaveShop = GSaveLandStart + 0x43be2c;
public override int ShopKabu => GSaveShop + 0x2d40; // part of shop; tailor & zakka increased size
public override int Museum => GSaveLandStart + 0x43f1e0;
public override int Visitor => GSaveLandStart + 0x4425e4;
public override int SaveFg => GSaveLandStart + 0x442814;
public override int BulletinBoard => GSaveLandStart + 0x443158;
public override int AirportThemeColor => GSaveLandStart + 0x523d60;
#endregion
#region GSaveLandOther
public const int GSaveLandOtherStart = 0x527ab0;
#region GSaveLandOther
public const int GSaveLandOtherStart = 0x527ab0;
public override int LostItemBox => GSaveLandOtherStart + 0x3408c0;
public override int LastSavedTime => GSaveLandOtherStart + 0x345220;
#endregion
public override int LostItemBox => GSaveLandOtherStart + 0x3408c0;
public override int LastSavedTime => GSaveLandOtherStart + 0x345220;
#endregion
public override int VillagerSize => Villager2.SIZE;
public override IVillager ReadVillager(byte[] data) => new Villager2(data);
public override int VillagerSize => Villager2.SIZE;
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 int VillagerHouseSize => VillagerHouse1.SIZE;
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 int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data);
}
}
public override int PlayerHouseSize => PlayerHouse1.SIZE;
public override IPlayerHouse ReadPlayerHouse(Memory<byte> data) => new PlayerHouse1(data);
public override int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(Memory<byte> data) => new PlayerRoom1(data);
}

View File

@ -1,67 +1,68 @@
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;
public override int PatternCount => PatternCount2;
#region GSaveLand
public const int GSaveLandStart = 0x110;
public override int Animal => GSaveLandStart + 0x10;
#region GSaveLand
public const int GSaveLandStart = 0x110;
public override int Animal => GSaveLandStart + 0x10;
public override int LandMyDesign => GSaveLandStart + 0x1e2610;
public override int PatternsPRO => LandMyDesign + (PatternCount2 * DesignPattern.SIZE);
public override int PatternFlag => PatternsPRO + (PatternCount2 * DesignPatternPRO.SIZE);
public override int PatternTailor => PatternFlag + DesignPattern.SIZE;
public override int LandMyDesign => GSaveLandStart + 0x1e2610;
public override int PatternsPRO => LandMyDesign + (PatternCount2 * DesignPattern.SIZE);
public override int PatternFlag => PatternsPRO + (PatternCount2 * DesignPatternPRO.SIZE);
public override int PatternTailor => PatternFlag + DesignPattern.SIZE;
public override int PatternsEditFlagStart => GSaveLandStart + 0x8BE150; // x100, HasPlayerEdited?
public override int PatternsProEditFlagStart => PatternsEditFlagStart + 0x64; // x100, HasPlayerEdited?
public override int PatternsEditFlagStart => GSaveLandStart + 0x8BE150; // x100, HasPlayerEdited?
public override int PatternsProEditFlagStart => PatternsEditFlagStart + 0x64; // x100, HasPlayerEdited?
public const int GSaveWeather = GSaveLandStart + 0x1e23c0;
public override int WeatherArea => GSaveWeather + 0x14; // Hemisphere
public override int WeatherRandSeed => GSaveWeather + 0x18;
public const int GSaveWeather = GSaveLandStart + 0x1e23c0;
public override int WeatherArea => GSaveWeather + 0x14; // Hemisphere
public override int WeatherRandSeed => GSaveWeather + 0x18;
public override int EventFlagLand => GSaveLandStart + 0x22d9b8;
public override int EventFlagLand => GSaveLandStart + 0x22d9b8;
// GSaveMainField
public const int GSaveMainFieldStart = GSaveLandStart + 0x22e1b8;
public override int FieldItem => GSaveMainFieldStart + 0x00000;
public override int LandMakingMap => GSaveMainFieldStart + 0xAAA00;
public override int MainFieldStructure => GSaveMainFieldStart + 0xCF600;
public override int OutsideField => GSaveMainFieldStart + 0xCF998;
public override int MyDesignMap => GSaveMainFieldStart + 0xCFA34;
// GSaveMainField
public const int GSaveMainFieldStart = GSaveLandStart + 0x22e1b8;
public override int FieldItem => GSaveMainFieldStart + 0x00000;
public override int LandMakingMap => GSaveMainFieldStart + 0xAAA00;
public override int MainFieldStructure => GSaveMainFieldStart + 0xCF600;
public override int OutsideField => GSaveMainFieldStart + 0xCF998;
public override int MyDesignMap => GSaveMainFieldStart + 0xCFA34;
public override int PlayerHouseList => GSaveLandStart + 0x308be4;
public override int NpcHouseList => GSaveLandStart + 0x43abe4;
public override int PlayerHouseList => GSaveLandStart + 0x308be4;
public override int NpcHouseList => GSaveLandStart + 0x43abe4;
public const int GSaveShop = GSaveLandStart + 0x43be2c;
public override int ShopKabu => GSaveShop + 0x2d40; // part of shop; tailor & zakka increased size
public override int Museum => GSaveLandStart + 0x43f1e0;
public override int Visitor => GSaveLandStart + 0x4425e4;
public override int SaveFg => GSaveLandStart + 0x442814;
public override int BulletinBoard => GSaveLandStart + 0x443158;
public override int AirportThemeColor => GSaveLandStart + 0x523d60;
#endregion
public const int GSaveShop = GSaveLandStart + 0x43be2c;
public override int ShopKabu => GSaveShop + 0x2d40; // part of shop; tailor & zakka increased size
public override int Museum => GSaveLandStart + 0x43f1e0;
public override int Visitor => GSaveLandStart + 0x4425e4;
public override int SaveFg => GSaveLandStart + 0x442814;
public override int BulletinBoard => GSaveLandStart + 0x443158;
public override int AirportThemeColor => GSaveLandStart + 0x523d60;
#endregion
#region GSaveLandOther
public const int GSaveLandOtherStart = 0x527ab0;
#region GSaveLandOther
public const int GSaveLandOtherStart = 0x527ab0;
public override int LostItemBox => GSaveLandOtherStart + 0x3408c0;
public override int LastSavedTime => GSaveLandOtherStart + 0x345220;
#endregion
public override int LostItemBox => GSaveLandOtherStart + 0x3408c0;
public override int LastSavedTime => GSaveLandOtherStart + 0x345220;
#endregion
public override int VillagerSize => Villager2.SIZE;
public override IVillager ReadVillager(byte[] data) => new Villager2(data);
public override int VillagerSize => Villager2.SIZE;
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 int VillagerHouseSize => VillagerHouse1.SIZE;
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 int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data);
}
}
public override int PlayerHouseSize => PlayerHouse1.SIZE;
public override IPlayerHouse ReadPlayerHouse(Memory<byte> data) => new PlayerHouse1(data);
public override int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(Memory<byte> data) => new PlayerRoom1(data);
}

View File

@ -1,64 +1,65 @@
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;
#region GSaveLand
public const int GSaveLandStart = 0x110;
public override int Animal => GSaveLandStart + 0x10;
public override int LandMyDesign => GSaveLandStart + 0x1D7200;
public override int PatternsPRO => LandMyDesign + (PatternCount * DesignPattern.SIZE);
public override int PatternFlag => PatternsPRO + (PatternCount * DesignPatternPRO.SIZE);
public override int PatternTailor => PatternFlag + DesignPattern.SIZE;
public override int LandMyDesign => GSaveLandStart + 0x1D7200;
public override int PatternsPRO => LandMyDesign + (PatternCount * DesignPattern.SIZE);
public override int PatternFlag => PatternsPRO + (PatternCount * DesignPatternPRO.SIZE);
public override int PatternTailor => PatternFlag + DesignPattern.SIZE;
public override int PatternsEditFlagStart => GSaveLandStart + 0x8BE150; // x100, HasPlayerEdited?
public override int PatternsProEditFlagStart => PatternsEditFlagStart + 0x64; // x100, HasPlayerEdited?
public override int PatternsEditFlagStart => GSaveLandStart + 0x8BE150; // x100, HasPlayerEdited?
public override int PatternsProEditFlagStart => PatternsEditFlagStart + 0x64; // x100, HasPlayerEdited?
public const int GSaveWeather = GSaveLandStart + 0x1D6FB0;
public override int WeatherArea => GSaveWeather + 0x14; // Hemisphere
public override int WeatherRandSeed => GSaveWeather + 0x18;
public const int GSaveWeather = GSaveLandStart + 0x1D6FB0;
public override int WeatherArea => GSaveWeather + 0x14; // Hemisphere
public override int WeatherRandSeed => GSaveWeather + 0x18;
public override int EventFlagLand => GSaveLandStart + 0x20100C;
public override int EventFlagLand => GSaveLandStart + 0x20100C;
// GSaveMainField
public const int GSaveMainFieldStart = GSaveLandStart + 0x20180C;
public override int FieldItem => GSaveMainFieldStart + 0x00000;
public override int LandMakingMap => GSaveMainFieldStart + 0xAAA00;
public override int MainFieldStructure => GSaveMainFieldStart + 0xCF600;
public override int OutsideField => GSaveMainFieldStart + 0xCF998;
public override int MyDesignMap => GSaveMainFieldStart + 0xCFA34;
// GSaveMainField
public const int GSaveMainFieldStart = GSaveLandStart + 0x20180C;
public override int FieldItem => GSaveMainFieldStart + 0x00000;
public override int LandMakingMap => GSaveMainFieldStart + 0xAAA00;
public override int MainFieldStructure => GSaveMainFieldStart + 0xCF600;
public override int OutsideField => GSaveMainFieldStart + 0xCF998;
public override int MyDesignMap => GSaveMainFieldStart + 0xCFA34;
public override int PlayerHouseList => GSaveLandStart + 0x2DC238;
public override int NpcHouseList => GSaveLandStart + 0x40E238;
public override int PlayerHouseList => GSaveLandStart + 0x2DC238;
public override int NpcHouseList => GSaveLandStart + 0x40E238;
public const int GSaveShop = GSaveLandStart + 0x40F480;
public override int ShopKabu => GSaveShop + 0x2AD0; // part of shop
public override int Museum => GSaveLandStart + 0x4124C4;
public override int Visitor => GSaveLandStart + 0x4158C8;
public override int SaveFg => GSaveLandStart + 0x415AF8;
public override int BulletinBoard => GSaveLandStart + 0x416440;
public override int AirportThemeColor => GSaveLandStart + 0x4F7048;
#endregion
public const int GSaveShop = GSaveLandStart + 0x40F480;
public override int ShopKabu => GSaveShop + 0x2AD0; // part of shop
public override int Museum => GSaveLandStart + 0x4124C4;
public override int Visitor => GSaveLandStart + 0x4158C8;
public override int SaveFg => GSaveLandStart + 0x415AF8;
public override int BulletinBoard => GSaveLandStart + 0x416440;
public override int AirportThemeColor => GSaveLandStart + 0x4F7048;
#endregion
#region GSaveLandOther
public const int GSaveLandOtherStart = 0x4FAD30;
#region GSaveLandOther
public const int GSaveLandOtherStart = 0x4FAD30;
public override int LostItemBox => GSaveLandOtherStart + 0x5CF370;
public override int LastSavedTime => GSaveLandOtherStart + 0x5D3CC8;
#endregion
public override int LostItemBox => GSaveLandOtherStart + 0x5CF370;
public override int LastSavedTime => GSaveLandOtherStart + 0x5D3CC8;
#endregion
public override int VillagerSize => Villager1.SIZE;
public override IVillager ReadVillager(byte[] data) => new Villager1(data);
public override int VillagerSize => Villager1.SIZE;
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 int VillagerHouseSize => VillagerHouse1.SIZE;
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 int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data);
}
}
public override int PlayerHouseSize => PlayerHouse1.SIZE;
public override IPlayerHouse ReadPlayerHouse(Memory<byte> data) => new PlayerHouse1(data);
public override int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(Memory<byte> data) => new PlayerRoom1(data);
}

View File

@ -1,64 +1,65 @@
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;
#region GSaveLand
public const int GSaveLandStart = 0x110;
public override int Animal => GSaveLandStart + 0x10;
public override int LandMyDesign => GSaveLandStart + 0x1D7200;
public override int PatternsPRO => LandMyDesign + (PatternCount * DesignPattern.SIZE);
public override int PatternFlag => PatternsPRO + (PatternCount * DesignPatternPRO.SIZE);
public override int PatternTailor => PatternFlag + DesignPattern.SIZE;
public override int LandMyDesign => GSaveLandStart + 0x1D7200;
public override int PatternsPRO => LandMyDesign + (PatternCount * DesignPattern.SIZE);
public override int PatternFlag => PatternsPRO + (PatternCount * DesignPatternPRO.SIZE);
public override int PatternTailor => PatternFlag + DesignPattern.SIZE;
public override int PatternsEditFlagStart => GSaveLandStart + 0x8BE150; // x100, HasPlayerEdited?
public override int PatternsProEditFlagStart => PatternsEditFlagStart + 0x64; // x100, HasPlayerEdited?
public override int PatternsEditFlagStart => GSaveLandStart + 0x8BE150; // x100, HasPlayerEdited?
public override int PatternsProEditFlagStart => PatternsEditFlagStart + 0x64; // x100, HasPlayerEdited?
public const int GSaveWeather = GSaveLandStart + 0x1D6FB0;
public override int WeatherArea => GSaveWeather + 0x14; // Hemisphere
public override int WeatherRandSeed => GSaveWeather + 0x18;
public const int GSaveWeather = GSaveLandStart + 0x1D6FB0;
public override int WeatherArea => GSaveWeather + 0x14; // Hemisphere
public override int WeatherRandSeed => GSaveWeather + 0x18;
public override int EventFlagLand => GSaveLandStart + 0x20100C;
public override int EventFlagLand => GSaveLandStart + 0x20100C;
// GSaveMainField
public const int GSaveMainFieldStart = GSaveLandStart + 0x20180C;
public override int FieldItem => GSaveMainFieldStart + 0x00000;
public override int LandMakingMap => GSaveMainFieldStart + 0xAAA00;
public override int MainFieldStructure => GSaveMainFieldStart + 0xCF600;
public override int OutsideField => GSaveMainFieldStart + 0xCF998;
public override int MyDesignMap => GSaveMainFieldStart + 0xCFA34;
// GSaveMainField
public const int GSaveMainFieldStart = GSaveLandStart + 0x20180C;
public override int FieldItem => GSaveMainFieldStart + 0x00000;
public override int LandMakingMap => GSaveMainFieldStart + 0xAAA00;
public override int MainFieldStructure => GSaveMainFieldStart + 0xCF600;
public override int OutsideField => GSaveMainFieldStart + 0xCF998;
public override int MyDesignMap => GSaveMainFieldStart + 0xCFA34;
public override int PlayerHouseList => GSaveLandStart + 0x2DC238;
public override int NpcHouseList => GSaveLandStart + 0x40E238;
public override int PlayerHouseList => GSaveLandStart + 0x2DC238;
public override int NpcHouseList => GSaveLandStart + 0x40E238;
public const int GSaveShop = GSaveLandStart + 0x40F480;
public override int ShopKabu => GSaveShop + 0x2AD0; // part of shop
public override int Museum => GSaveLandStart + 0x4124C4;
public override int Visitor => GSaveLandStart + 0x4158C8;
public override int SaveFg => GSaveLandStart + 0x415AF8;
public override int BulletinBoard => GSaveLandStart + 0x416440;
public override int AirportThemeColor => GSaveLandStart + 0x4F7048;
#endregion
public const int GSaveShop = GSaveLandStart + 0x40F480;
public override int ShopKabu => GSaveShop + 0x2AD0; // part of shop
public override int Museum => GSaveLandStart + 0x4124C4;
public override int Visitor => GSaveLandStart + 0x4158C8;
public override int SaveFg => GSaveLandStart + 0x415AF8;
public override int BulletinBoard => GSaveLandStart + 0x416440;
public override int AirportThemeColor => GSaveLandStart + 0x4F7048;
#endregion
#region GSaveLandOther
public const int GSaveLandOtherStart = 0x4FAD60;
#region GSaveLandOther
public const int GSaveLandOtherStart = 0x4FAD60;
public override int LostItemBox => GSaveLandOtherStart + 0x5CF3F0;
public override int LastSavedTime => GSaveLandOtherStart + 0x5D3D48;
#endregion
public override int LostItemBox => GSaveLandOtherStart + 0x5CF3F0;
public override int LastSavedTime => GSaveLandOtherStart + 0x5D3D48;
#endregion
public override int VillagerSize => Villager1.SIZE;
public override IVillager ReadVillager(byte[] data) => new Villager1(data);
public override int VillagerSize => Villager1.SIZE;
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 int VillagerHouseSize => VillagerHouse1.SIZE;
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 int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data);
}
}
public override int PlayerHouseSize => PlayerHouse1.SIZE;
public override IPlayerHouse ReadPlayerHouse(Memory<byte> data) => new PlayerHouse1(data);
public override int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(Memory<byte> data) => new PlayerRoom1(data);
}

View File

@ -1,64 +1,65 @@
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;
#region GSaveLand
public const int GSaveLandStart = 0x110;
public override int Animal => GSaveLandStart + 0x10;
public override int LandMyDesign => GSaveLandStart + 0x1D7200;
public override int PatternsPRO => LandMyDesign + (PatternCount * DesignPattern.SIZE);
public override int PatternFlag => PatternsPRO + (PatternCount * DesignPatternPRO.SIZE);
public override int PatternTailor => PatternFlag + DesignPattern.SIZE;
public override int LandMyDesign => GSaveLandStart + 0x1D7200;
public override int PatternsPRO => LandMyDesign + (PatternCount * DesignPattern.SIZE);
public override int PatternFlag => PatternsPRO + (PatternCount * DesignPatternPRO.SIZE);
public override int PatternTailor => PatternFlag + DesignPattern.SIZE;
public override int PatternsEditFlagStart => GSaveLandStart + 0x8BE150; // x100, HasPlayerEdited?
public override int PatternsProEditFlagStart => PatternsEditFlagStart + 0x64; // x100, HasPlayerEdited?
public override int PatternsEditFlagStart => GSaveLandStart + 0x8BE150; // x100, HasPlayerEdited?
public override int PatternsProEditFlagStart => PatternsEditFlagStart + 0x64; // x100, HasPlayerEdited?
public const int GSaveWeather = GSaveLandStart + 0x1D6FB0;
public override int WeatherArea => GSaveWeather + 0x14; // Hemisphere
public override int WeatherRandSeed => GSaveWeather + 0x18;
public const int GSaveWeather = GSaveLandStart + 0x1D6FB0;
public override int WeatherArea => GSaveWeather + 0x14; // Hemisphere
public override int WeatherRandSeed => GSaveWeather + 0x18;
public override int EventFlagLand => GSaveLandStart + 0x20100C;
public override int EventFlagLand => GSaveLandStart + 0x20100C;
// GSaveMainField
public const int GSaveMainFieldStart = GSaveLandStart + 0x20180C;
public override int FieldItem => GSaveMainFieldStart + 0x00000;
public override int LandMakingMap => GSaveMainFieldStart + 0xAAA00;
public override int MainFieldStructure => GSaveMainFieldStart + 0xCF600;
public override int OutsideField => GSaveMainFieldStart + 0xCF998;
public override int MyDesignMap => GSaveMainFieldStart + 0xCFA34;
// GSaveMainField
public const int GSaveMainFieldStart = GSaveLandStart + 0x20180C;
public override int FieldItem => GSaveMainFieldStart + 0x00000;
public override int LandMakingMap => GSaveMainFieldStart + 0xAAA00;
public override int MainFieldStructure => GSaveMainFieldStart + 0xCF600;
public override int OutsideField => GSaveMainFieldStart + 0xCF998;
public override int MyDesignMap => GSaveMainFieldStart + 0xCFA34;
public override int PlayerHouseList => GSaveLandStart + 0x2DC238;
public override int NpcHouseList => GSaveLandStart + 0x40E238;
public override int PlayerHouseList => GSaveLandStart + 0x2DC238;
public override int NpcHouseList => GSaveLandStart + 0x40E238;
public const int GSaveShop = GSaveLandStart + 0x40F480;
public override int ShopKabu => GSaveShop + 0x2AD0; // part of shop
public override int Museum => GSaveLandStart + 0x4124C4;
public override int Visitor => GSaveLandStart + 0x4158C8;
public override int SaveFg => GSaveLandStart + 0x415AF8;
public override int BulletinBoard => GSaveLandStart + 0x416440;
public override int AirportThemeColor => GSaveLandStart + 0x4F7048;
#endregion
public const int GSaveShop = GSaveLandStart + 0x40F480;
public override int ShopKabu => GSaveShop + 0x2AD0; // part of shop
public override int Museum => GSaveLandStart + 0x4124C4;
public override int Visitor => GSaveLandStart + 0x4158C8;
public override int SaveFg => GSaveLandStart + 0x415AF8;
public override int BulletinBoard => GSaveLandStart + 0x416440;
public override int AirportThemeColor => GSaveLandStart + 0x4F7048;
#endregion
#region GSaveLandOther
public const int GSaveLandOtherStart = 0x4FADA0;
#region GSaveLandOther
public const int GSaveLandOtherStart = 0x4FADA0;
public override int LostItemBox => GSaveLandOtherStart + 0x605E70;
public override int LastSavedTime => GSaveLandOtherStart + 0x60A708;
#endregion
public override int LostItemBox => GSaveLandOtherStart + 0x605E70;
public override int LastSavedTime => GSaveLandOtherStart + 0x60A708;
#endregion
public override int VillagerSize => Villager1.SIZE;
public override IVillager ReadVillager(byte[] data) => new Villager1(data);
public override int VillagerSize => Villager1.SIZE;
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 int VillagerHouseSize => VillagerHouse1.SIZE;
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 int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data);
}
}
public override int PlayerHouseSize => PlayerHouse1.SIZE;
public override IPlayerHouse ReadPlayerHouse(Memory<byte> data) => new PlayerHouse1(data);
public override int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(Memory<byte> data) => new PlayerRoom1(data);
}

View File

@ -1,64 +1,65 @@
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;
#region GSaveLand
public const int GSaveLandStart = 0x110;
public override int Animal => GSaveLandStart + 0x10;
public override int LandMyDesign => GSaveLandStart + 0x1e2600;
public override int PatternsPRO => LandMyDesign + (PatternCount * DesignPattern.SIZE);
public override int PatternFlag => PatternsPRO + (PatternCount * DesignPatternPRO.SIZE);
public override int PatternTailor => PatternFlag + DesignPattern.SIZE;
public override int LandMyDesign => GSaveLandStart + 0x1e2600;
public override int PatternsPRO => LandMyDesign + (PatternCount * DesignPattern.SIZE);
public override int PatternFlag => PatternsPRO + (PatternCount * DesignPatternPRO.SIZE);
public override int PatternTailor => PatternFlag + DesignPattern.SIZE;
public override int PatternsEditFlagStart => GSaveLandStart + 0x8BE150; // x100, HasPlayerEdited?
public override int PatternsProEditFlagStart => PatternsEditFlagStart + 0x64; // x100, HasPlayerEdited?
public override int PatternsEditFlagStart => GSaveLandStart + 0x8BE150; // x100, HasPlayerEdited?
public override int PatternsProEditFlagStart => PatternsEditFlagStart + 0x64; // x100, HasPlayerEdited?
public const int GSaveWeather = GSaveLandStart + 0x1e23B0;
public override int WeatherArea => GSaveWeather + 0x14; // Hemisphere
public override int WeatherRandSeed => GSaveWeather + 0x18;
public const int GSaveWeather = GSaveLandStart + 0x1e23B0;
public override int WeatherArea => GSaveWeather + 0x14; // Hemisphere
public override int WeatherRandSeed => GSaveWeather + 0x18;
public override int EventFlagLand => GSaveLandStart + 0x20c40c;
public override int EventFlagLand => GSaveLandStart + 0x20c40c;
// GSaveMainField
public const int GSaveMainFieldStart = GSaveLandStart + 0x20cc0c;
public override int FieldItem => GSaveMainFieldStart + 0x00000;
public override int LandMakingMap => GSaveMainFieldStart + 0xAAA00;
public override int MainFieldStructure => GSaveMainFieldStart + 0xCF600;
public override int OutsideField => GSaveMainFieldStart + 0xCF998;
public override int MyDesignMap => GSaveMainFieldStart + 0xCFA34;
// GSaveMainField
public const int GSaveMainFieldStart = GSaveLandStart + 0x20cc0c;
public override int FieldItem => GSaveMainFieldStart + 0x00000;
public override int LandMakingMap => GSaveMainFieldStart + 0xAAA00;
public override int MainFieldStructure => GSaveMainFieldStart + 0xCF600;
public override int OutsideField => GSaveMainFieldStart + 0xCF998;
public override int MyDesignMap => GSaveMainFieldStart + 0xCFA34;
public override int PlayerHouseList => GSaveLandStart + 0x2e7638;
public override int NpcHouseList => GSaveLandStart + 0x419638;
public override int PlayerHouseList => GSaveLandStart + 0x2e7638;
public override int NpcHouseList => GSaveLandStart + 0x419638;
public const int GSaveShop = GSaveLandStart + 0x41a880;
public override int ShopKabu => GSaveShop + 0x2b10; // part of shop
public override int Museum => GSaveLandStart + 0x41d904;
public override int Visitor => GSaveLandStart + 0x420d08;
public override int SaveFg => GSaveLandStart + 0x420f38;
public override int BulletinBoard => GSaveLandStart + 0x421880;
public override int AirportThemeColor => GSaveLandStart + 0x502488;
#endregion
public const int GSaveShop = GSaveLandStart + 0x41a880;
public override int ShopKabu => GSaveShop + 0x2b10; // part of shop
public override int Museum => GSaveLandStart + 0x41d904;
public override int Visitor => GSaveLandStart + 0x420d08;
public override int SaveFg => GSaveLandStart + 0x420f38;
public override int BulletinBoard => GSaveLandStart + 0x421880;
public override int AirportThemeColor => GSaveLandStart + 0x502488;
#endregion
#region GSaveLandOther
public const int GSaveLandOtherStart = 0x5061e0;
#region GSaveLandOther
public const int GSaveLandOtherStart = 0x5061e0;
public override int LostItemBox => GSaveLandOtherStart + 0x6159f0;
public override int LastSavedTime => GSaveLandOtherStart + 0x61a288;
#endregion
public override int LostItemBox => GSaveLandOtherStart + 0x6159f0;
public override int LastSavedTime => GSaveLandOtherStart + 0x61a288;
#endregion
public override int VillagerSize => Villager2.SIZE;
public override IVillager ReadVillager(byte[] data) => new Villager2(data);
public override int VillagerSize => Villager2.SIZE;
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 int VillagerHouseSize => VillagerHouse1.SIZE;
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 int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data);
}
}
public override int PlayerHouseSize => PlayerHouse1.SIZE;
public override IPlayerHouse ReadPlayerHouse(Memory<byte> data) => new PlayerHouse1(data);
public override int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(Memory<byte> data) => new PlayerRoom1(data);
}

View File

@ -1,64 +1,65 @@
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;
#region GSaveLand
public const int GSaveLandStart = 0x110;
public override int Animal => GSaveLandStart + 0x10;
public override int LandMyDesign => GSaveLandStart + 0x1e2600;
public override int PatternsPRO => LandMyDesign + (PatternCount * DesignPattern.SIZE);
public override int PatternFlag => PatternsPRO + (PatternCount * DesignPatternPRO.SIZE);
public override int PatternTailor => PatternFlag + DesignPattern.SIZE;
public override int LandMyDesign => GSaveLandStart + 0x1e2600;
public override int PatternsPRO => LandMyDesign + (PatternCount * DesignPattern.SIZE);
public override int PatternFlag => PatternsPRO + (PatternCount * DesignPatternPRO.SIZE);
public override int PatternTailor => PatternFlag + DesignPattern.SIZE;
public override int PatternsEditFlagStart => GSaveLandStart + 0x8BE150; // x100, HasPlayerEdited?
public override int PatternsProEditFlagStart => PatternsEditFlagStart + 0x64; // x100, HasPlayerEdited?
public override int PatternsEditFlagStart => GSaveLandStart + 0x8BE150; // x100, HasPlayerEdited?
public override int PatternsProEditFlagStart => PatternsEditFlagStart + 0x64; // x100, HasPlayerEdited?
public const int GSaveWeather = GSaveLandStart + 0x1e23B0;
public override int WeatherArea => GSaveWeather + 0x14; // Hemisphere
public override int WeatherRandSeed => GSaveWeather + 0x18;
public const int GSaveWeather = GSaveLandStart + 0x1e23B0;
public override int WeatherArea => GSaveWeather + 0x14; // Hemisphere
public override int WeatherRandSeed => GSaveWeather + 0x18;
public override int EventFlagLand => GSaveLandStart + 0x20c40c;
public override int EventFlagLand => GSaveLandStart + 0x20c40c;
// GSaveMainField
public const int GSaveMainFieldStart = GSaveLandStart + 0x20cc0c;
public override int FieldItem => GSaveMainFieldStart + 0x00000;
public override int LandMakingMap => GSaveMainFieldStart + 0xAAA00;
public override int MainFieldStructure => GSaveMainFieldStart + 0xCF600;
public override int OutsideField => GSaveMainFieldStart + 0xCF998;
public override int MyDesignMap => GSaveMainFieldStart + 0xCFA34;
// GSaveMainField
public const int GSaveMainFieldStart = GSaveLandStart + 0x20cc0c;
public override int FieldItem => GSaveMainFieldStart + 0x00000;
public override int LandMakingMap => GSaveMainFieldStart + 0xAAA00;
public override int MainFieldStructure => GSaveMainFieldStart + 0xCF600;
public override int OutsideField => GSaveMainFieldStart + 0xCF998;
public override int MyDesignMap => GSaveMainFieldStart + 0xCFA34;
public override int PlayerHouseList => GSaveLandStart + 0x2e7638;
public override int NpcHouseList => GSaveLandStart + 0x419638;
public override int PlayerHouseList => GSaveLandStart + 0x2e7638;
public override int NpcHouseList => GSaveLandStart + 0x419638;
public const int GSaveShop = GSaveLandStart + 0x41a880;
public override int ShopKabu => GSaveShop + 0x2be0; // part of shop; tailor increased size
public override int Museum => GSaveLandStart + 0x41d9d4;
public override int Visitor => GSaveLandStart + 0x420dd8;
public override int SaveFg => GSaveLandStart + 0x421008;
public override int BulletinBoard => GSaveLandStart + 0x421950;
public override int AirportThemeColor => GSaveLandStart + 0x502558;
#endregion
public const int GSaveShop = GSaveLandStart + 0x41a880;
public override int ShopKabu => GSaveShop + 0x2be0; // part of shop; tailor increased size
public override int Museum => GSaveLandStart + 0x41d9d4;
public override int Visitor => GSaveLandStart + 0x420dd8;
public override int SaveFg => GSaveLandStart + 0x421008;
public override int BulletinBoard => GSaveLandStart + 0x421950;
public override int AirportThemeColor => GSaveLandStart + 0x502558;
#endregion
#region GSaveLandOther
public const int GSaveLandOtherStart = 0x5062b0;
#region GSaveLandOther
public const int GSaveLandOtherStart = 0x5062b0;
public override int LostItemBox => GSaveLandOtherStart + 0x61a4f0;
public override int LastSavedTime => GSaveLandOtherStart + 0x61ed88;
#endregion
public override int LostItemBox => GSaveLandOtherStart + 0x61a4f0;
public override int LastSavedTime => GSaveLandOtherStart + 0x61ed88;
#endregion
public override int VillagerSize => Villager2.SIZE;
public override IVillager ReadVillager(byte[] data) => new Villager2(data);
public override int VillagerSize => Villager2.SIZE;
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 int VillagerHouseSize => VillagerHouse1.SIZE;
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 int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data);
}
}
public override int PlayerHouseSize => PlayerHouse1.SIZE;
public override IPlayerHouse ReadPlayerHouse(Memory<byte> data) => new PlayerHouse1(data);
public override int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(Memory<byte> data) => new PlayerRoom1(data);
}

View File

@ -1,64 +1,65 @@
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;
#region GSaveLand
public const int GSaveLandStart = 0x110;
public override int Animal => GSaveLandStart + 0x10;
public override int LandMyDesign => GSaveLandStart + 0x1e2600;
public override int PatternsPRO => LandMyDesign + (PatternCount * DesignPattern.SIZE);
public override int PatternFlag => PatternsPRO + (PatternCount * DesignPatternPRO.SIZE);
public override int PatternTailor => PatternFlag + DesignPattern.SIZE;
public override int LandMyDesign => GSaveLandStart + 0x1e2600;
public override int PatternsPRO => LandMyDesign + (PatternCount * DesignPattern.SIZE);
public override int PatternFlag => PatternsPRO + (PatternCount * DesignPatternPRO.SIZE);
public override int PatternTailor => PatternFlag + DesignPattern.SIZE;
public override int PatternsEditFlagStart => GSaveLandStart + 0x8BE150; // x100, HasPlayerEdited?
public override int PatternsProEditFlagStart => PatternsEditFlagStart + 0x64; // x100, HasPlayerEdited?
public override int PatternsEditFlagStart => GSaveLandStart + 0x8BE150; // x100, HasPlayerEdited?
public override int PatternsProEditFlagStart => PatternsEditFlagStart + 0x64; // x100, HasPlayerEdited?
public const int GSaveWeather = GSaveLandStart + 0x1e23B0;
public override int WeatherArea => GSaveWeather + 0x14; // Hemisphere
public override int WeatherRandSeed => GSaveWeather + 0x18;
public const int GSaveWeather = GSaveLandStart + 0x1e23B0;
public override int WeatherArea => GSaveWeather + 0x14; // Hemisphere
public override int WeatherRandSeed => GSaveWeather + 0x18;
public override int EventFlagLand => GSaveLandStart + 0x20a408;
public override int EventFlagLand => GSaveLandStart + 0x20a408;
// GSaveMainField
public const int GSaveMainFieldStart = GSaveLandStart + 0x20ac08;
public override int FieldItem => GSaveMainFieldStart + 0x00000;
public override int LandMakingMap => GSaveMainFieldStart + 0xAAA00;
public override int MainFieldStructure => GSaveMainFieldStart + 0xCF600;
public override int OutsideField => GSaveMainFieldStart + 0xCF998;
public override int MyDesignMap => GSaveMainFieldStart + 0xCFA34;
// GSaveMainField
public const int GSaveMainFieldStart = GSaveLandStart + 0x20ac08;
public override int FieldItem => GSaveMainFieldStart + 0x00000;
public override int LandMakingMap => GSaveMainFieldStart + 0xAAA00;
public override int MainFieldStructure => GSaveMainFieldStart + 0xCF600;
public override int OutsideField => GSaveMainFieldStart + 0xCF998;
public override int MyDesignMap => GSaveMainFieldStart + 0xCFA34;
public override int PlayerHouseList => GSaveLandStart + 0x2e5634;
public override int NpcHouseList => GSaveLandStart + 0x417634;
public override int PlayerHouseList => GSaveLandStart + 0x2e5634;
public override int NpcHouseList => GSaveLandStart + 0x417634;
public const int GSaveShop = GSaveLandStart + 0x41887c;
public override int ShopKabu => GSaveShop + 0x2cb0; // part of shop; tailor increased size
public override int Museum => GSaveLandStart + 0x41bba0;
public override int Visitor => GSaveLandStart + 0x41efa4;
public override int SaveFg => GSaveLandStart + 0x41f1d4;
public override int BulletinBoard => GSaveLandStart + 0x41fb18;
public override int AirportThemeColor => GSaveLandStart + 0x500720;
#endregion
public const int GSaveShop = GSaveLandStart + 0x41887c;
public override int ShopKabu => GSaveShop + 0x2cb0; // part of shop; tailor increased size
public override int Museum => GSaveLandStart + 0x41bba0;
public override int Visitor => GSaveLandStart + 0x41efa4;
public override int SaveFg => GSaveLandStart + 0x41f1d4;
public override int BulletinBoard => GSaveLandStart + 0x41fb18;
public override int AirportThemeColor => GSaveLandStart + 0x500720;
#endregion
#region GSaveLandOther
public const int GSaveLandOtherStart = 0x504470;
#region GSaveLandOther
public const int GSaveLandOtherStart = 0x504470;
public override int LostItemBox => GSaveLandOtherStart + 0x340680;
public override int LastSavedTime => GSaveLandOtherStart + 0x344f18;
#endregion
public override int LostItemBox => GSaveLandOtherStart + 0x340680;
public override int LastSavedTime => GSaveLandOtherStart + 0x344f18;
#endregion
public override int VillagerSize => Villager2.SIZE;
public override IVillager ReadVillager(byte[] data) => new Villager2(data);
public override int VillagerSize => Villager2.SIZE;
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 int VillagerHouseSize => VillagerHouse1.SIZE;
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 int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data);
}
}
public override int PlayerHouseSize => PlayerHouse1.SIZE;
public override IPlayerHouse ReadPlayerHouse(Memory<byte> data) => new PlayerHouse1(data);
public override int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(Memory<byte> data) => new PlayerRoom1(data);
}

View File

@ -1,65 +1,66 @@
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;
#region GSaveLand
public const int GSaveLandStart = 0x110;
public override int Animal => GSaveLandStart + 0x10;
public override int LandMyDesign => GSaveLandStart + 0x1e2600;
public override int PatternsPRO => LandMyDesign + (PatternCount * DesignPattern.SIZE);
public override int PatternFlag => PatternsPRO + (PatternCount * DesignPatternPRO.SIZE);
public override int PatternTailor => PatternFlag + DesignPattern.SIZE;
public override int LandMyDesign => GSaveLandStart + 0x1e2600;
public override int PatternsPRO => LandMyDesign + (PatternCount * DesignPattern.SIZE);
public override int PatternFlag => PatternsPRO + (PatternCount * DesignPatternPRO.SIZE);
public override int PatternTailor => PatternFlag + DesignPattern.SIZE;
public override int PatternsEditFlagStart => GSaveLandStart + 0x8BE150; // x100, HasPlayerEdited?
public override int PatternsProEditFlagStart => PatternsEditFlagStart + 0x64; // x100, HasPlayerEdited?
public override int PatternsEditFlagStart => GSaveLandStart + 0x8BE150; // x100, HasPlayerEdited?
public override int PatternsProEditFlagStart => PatternsEditFlagStart + 0x64; // x100, HasPlayerEdited?
public const int GSaveWeather = GSaveLandStart + 0x1e23B0;
public override int WeatherArea => GSaveWeather + 0x14; // Hemisphere
public override int WeatherRandSeed => GSaveWeather + 0x18;
public const int GSaveWeather = GSaveLandStart + 0x1e23B0;
public override int WeatherArea => GSaveWeather + 0x14; // Hemisphere
public override int WeatherRandSeed => GSaveWeather + 0x18;
public override int EventFlagLand => GSaveLandStart + 0x20a408;
public override int EventFlagLand => GSaveLandStart + 0x20a408;
// GSaveMainField
public const int GSaveMainFieldStart = GSaveLandStart + 0x20ac08;
public override int FieldItem => GSaveMainFieldStart + 0x00000;
public override int LandMakingMap => GSaveMainFieldStart + 0xAAA00;
public override int MainFieldStructure => GSaveMainFieldStart + 0xCF600;
public override int OutsideField => GSaveMainFieldStart + 0xCF998;
public override int MyDesignMap => GSaveMainFieldStart + 0xCFA34;
// GSaveMainField
public const int GSaveMainFieldStart = GSaveLandStart + 0x20ac08;
public override int FieldItem => GSaveMainFieldStart + 0x00000;
public override int LandMakingMap => GSaveMainFieldStart + 0xAAA00;
public override int MainFieldStructure => GSaveMainFieldStart + 0xCF600;
public override int OutsideField => GSaveMainFieldStart + 0xCF998;
public override int MyDesignMap => GSaveMainFieldStart + 0xCFA34;
public override int PlayerHouseList => GSaveLandStart + 0x2e5634;
public override int NpcHouseList => GSaveLandStart + 0x417634;
public override int PlayerHouseList => GSaveLandStart + 0x2e5634;
public override int NpcHouseList => GSaveLandStart + 0x417634;
public const int GSaveShop = GSaveLandStart + 0x41887c;
public override int ShopKabu => GSaveShop + 0x2cb0; // part of shop; tailor increased size
public override int Museum => GSaveLandStart + 0x41bba0;
public override int Visitor => GSaveLandStart + 0x41efa4;
public override int SaveFg => GSaveLandStart + 0x41f1d4;
public override int BulletinBoard => GSaveLandStart + 0x41fb18;
public override int AirportThemeColor => GSaveLandStart + 0x500720;
#endregion
public const int GSaveShop = GSaveLandStart + 0x41887c;
public override int ShopKabu => GSaveShop + 0x2cb0; // part of shop; tailor increased size
public override int Museum => GSaveLandStart + 0x41bba0;
public override int Visitor => GSaveLandStart + 0x41efa4;
public override int SaveFg => GSaveLandStart + 0x41f1d4;
public override int BulletinBoard => GSaveLandStart + 0x41fb18;
public override int AirportThemeColor => GSaveLandStart + 0x500720;
#endregion
#region GSaveLandOther
public const int GSaveLandOtherStart = 0x504470;
#region GSaveLandOther
public const int GSaveLandOtherStart = 0x504470;
public override int LostItemBox => GSaveLandOtherStart + 0x340680;
public override int LastSavedTime => GSaveLandOtherStart + 0x344f18;
#endregion
public override int LostItemBox => GSaveLandOtherStart + 0x340680;
public override int LastSavedTime => GSaveLandOtherStart + 0x344f18;
#endregion
public override int VillagerSize => Villager2.SIZE;
public override IVillager ReadVillager(byte[] data) => new Villager2(data);
public override int VillagerSize => Villager2.SIZE;
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 int VillagerHouseSize => VillagerHouse1.SIZE;
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 int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data);
}
}
public override int PlayerHouseSize => PlayerHouse1.SIZE;
public override IPlayerHouse ReadPlayerHouse(Memory<byte> data) => new PlayerHouse1(data);
public override int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(Memory<byte> data) => new PlayerRoom1(data);
}

View File

@ -1,66 +1,67 @@
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;
public override int PatternCount => PatternCount2;
#region GSaveLand
public const int GSaveLandStart = 0x110;
public override int Animal => GSaveLandStart + 0x10;
#region GSaveLand
public const int GSaveLandStart = 0x110;
public override int Animal => GSaveLandStart + 0x10;
public override int LandMyDesign => GSaveLandStart + 0x1e2600;
public override int PatternsPRO => LandMyDesign + (PatternCount2 * DesignPattern.SIZE);
public override int PatternFlag => PatternsPRO + (PatternCount2 * DesignPatternPRO.SIZE);
public override int PatternTailor => PatternFlag + DesignPattern.SIZE;
public override int LandMyDesign => GSaveLandStart + 0x1e2600;
public override int PatternsPRO => LandMyDesign + (PatternCount2 * DesignPattern.SIZE);
public override int PatternFlag => PatternsPRO + (PatternCount2 * DesignPatternPRO.SIZE);
public override int PatternTailor => PatternFlag + DesignPattern.SIZE;
public override int PatternsEditFlagStart => GSaveLandStart + 0x8BE150; // x100, HasPlayerEdited?
public override int PatternsProEditFlagStart => PatternsEditFlagStart + 0x64; // x100, HasPlayerEdited?
public override int PatternsEditFlagStart => GSaveLandStart + 0x8BE150; // x100, HasPlayerEdited?
public override int PatternsProEditFlagStart => PatternsEditFlagStart + 0x64; // x100, HasPlayerEdited?
public const int GSaveWeather = GSaveLandStart + 0x1e23B0;
public override int WeatherArea => GSaveWeather + 0x14; // Hemisphere
public override int WeatherRandSeed => GSaveWeather + 0x18;
public const int GSaveWeather = GSaveLandStart + 0x1e23B0;
public override int WeatherArea => GSaveWeather + 0x14; // Hemisphere
public override int WeatherRandSeed => GSaveWeather + 0x18;
public override int EventFlagLand => GSaveLandStart + 0x22d9a8;
public override int EventFlagLand => GSaveLandStart + 0x22d9a8;
// GSaveMainField
public const int GSaveMainFieldStart = GSaveLandStart + 0x22e1a8;
public override int FieldItem => GSaveMainFieldStart + 0x00000;
public override int LandMakingMap => GSaveMainFieldStart + 0xAAA00;
public override int MainFieldStructure => GSaveMainFieldStart + 0xCF600;
public override int OutsideField => GSaveMainFieldStart + 0xCF998;
public override int MyDesignMap => GSaveMainFieldStart + 0xCFA34;
// GSaveMainField
public const int GSaveMainFieldStart = GSaveLandStart + 0x22e1a8;
public override int FieldItem => GSaveMainFieldStart + 0x00000;
public override int LandMakingMap => GSaveMainFieldStart + 0xAAA00;
public override int MainFieldStructure => GSaveMainFieldStart + 0xCF600;
public override int OutsideField => GSaveMainFieldStart + 0xCF998;
public override int MyDesignMap => GSaveMainFieldStart + 0xCFA34;
public override int PlayerHouseList => GSaveLandStart + 0x308bd4;
public override int NpcHouseList => GSaveLandStart + 0x43abd4;
public override int PlayerHouseList => GSaveLandStart + 0x308bd4;
public override int NpcHouseList => GSaveLandStart + 0x43abd4;
public const int GSaveShop = GSaveLandStart + 0x43be1c;
public override int ShopKabu => GSaveShop + 0x2d40; // part of shop; tailor & zakka increased size
public override int Museum => GSaveLandStart + 0x43f1d0;
public override int Visitor => GSaveLandStart + 0x4425d4;
public override int SaveFg => GSaveLandStart + 0x442804;
public override int BulletinBoard => GSaveLandStart + 0x443148;
public override int AirportThemeColor => GSaveLandStart + 0x523d50;
#endregion
public const int GSaveShop = GSaveLandStart + 0x43be1c;
public override int ShopKabu => GSaveShop + 0x2d40; // part of shop; tailor & zakka increased size
public override int Museum => GSaveLandStart + 0x43f1d0;
public override int Visitor => GSaveLandStart + 0x4425d4;
public override int SaveFg => GSaveLandStart + 0x442804;
public override int BulletinBoard => GSaveLandStart + 0x443148;
public override int AirportThemeColor => GSaveLandStart + 0x523d50;
#endregion
#region GSaveLandOther
public const int GSaveLandOtherStart = 0x527aa0;
#region GSaveLandOther
public const int GSaveLandOtherStart = 0x527aa0;
public override int LostItemBox => GSaveLandOtherStart + 0x3408c0;
public override int LastSavedTime => GSaveLandOtherStart + 0x345220;
#endregion
public override int LostItemBox => GSaveLandOtherStart + 0x3408c0;
public override int LastSavedTime => GSaveLandOtherStart + 0x345220;
#endregion
public override int VillagerSize => Villager2.SIZE;
public override IVillager ReadVillager(byte[] data) => new Villager2(data);
public override int VillagerSize => Villager2.SIZE;
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 int VillagerHouseSize => VillagerHouse1.SIZE;
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 int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom1(data);
}
}
public override int PlayerHouseSize => PlayerHouse1.SIZE;
public override IPlayerHouse ReadPlayerHouse(Memory<byte> data) => new PlayerHouse1(data);
public override int PlayerRoomSize => PlayerRoom1.SIZE;
public override IPlayerRoom ReadPlayerRoom(Memory<byte> data) => new PlayerRoom1(data);
}

View File

@ -1,67 +1,68 @@
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;
public override int PatternCount => PatternCount2;
#region GSaveLand
public const int GSaveLandStart = 0x110;
public override int Animal => GSaveLandStart + 0x10;
#region GSaveLand
public const int GSaveLandStart = 0x110;
public override int Animal => GSaveLandStart + 0x10;
public override int LandMyDesign => GSaveLandStart + 0x1e3848;
public override int PatternsPRO => LandMyDesign + (PatternCount2 * DesignPattern.SIZE);
public override int PatternFlag => PatternsPRO + (PatternCount2 * DesignPatternPRO.SIZE);
public override int PatternTailor => PatternFlag + DesignPattern.SIZE;
public override int LandMyDesign => GSaveLandStart + 0x1e3848;
public override int PatternsPRO => LandMyDesign + (PatternCount2 * DesignPattern.SIZE);
public override int PatternFlag => PatternsPRO + (PatternCount2 * DesignPatternPRO.SIZE);
public override int PatternTailor => PatternFlag + DesignPattern.SIZE;
public override int PatternsEditFlagStart => GSaveLandStart + 0x8BE150; // x100, HasPlayerEdited?
public override int PatternsProEditFlagStart => PatternsEditFlagStart + 0x64; // x100, HasPlayerEdited?
public override int PatternsEditFlagStart => GSaveLandStart + 0x8BE150; // x100, HasPlayerEdited?
public override int PatternsProEditFlagStart => PatternsEditFlagStart + 0x64; // x100, HasPlayerEdited?
public const int GSaveWeather = GSaveLandStart + 0x1e35f0;
public override int WeatherArea => GSaveWeather + 0x14; // Hemisphere
public override int WeatherRandSeed => GSaveWeather + 0x18;
public const int GSaveWeather = GSaveLandStart + 0x1e35f0;
public override int WeatherArea => GSaveWeather + 0x14; // Hemisphere
public override int WeatherRandSeed => GSaveWeather + 0x18;
public override int EventFlagLand => GSaveLandStart + 0x22ebf0;
public override int EventFlagLand => GSaveLandStart + 0x22ebf0;
// GSaveMainField
public const int GSaveMainFieldStart = GSaveLandStart + 0x22f3f0;
public override int FieldItem => GSaveMainFieldStart + 0x00000;
public override int LandMakingMap => GSaveMainFieldStart + 0xAAA00;
public override int MainFieldStructure => GSaveMainFieldStart + 0xCF600;
public override int OutsideField => GSaveMainFieldStart + 0xCF998;
public override int MyDesignMap => GSaveMainFieldStart + 0xCFA34;
// GSaveMainField
public const int GSaveMainFieldStart = GSaveLandStart + 0x22f3f0;
public override int FieldItem => GSaveMainFieldStart + 0x00000;
public override int LandMakingMap => GSaveMainFieldStart + 0xAAA00;
public override int MainFieldStructure => GSaveMainFieldStart + 0xCF600;
public override int OutsideField => GSaveMainFieldStart + 0xCF998;
public override int MyDesignMap => GSaveMainFieldStart + 0xCFA34;
public override int PlayerHouseList => GSaveLandStart + 0x30a6bc;
public override int NpcHouseList => GSaveLandStart + 0x44f7fc;
public override int PlayerHouseList => GSaveLandStart + 0x30a6bc;
public override int NpcHouseList => GSaveLandStart + 0x44f7fc;
public const int GSaveShop = GSaveLandStart + 0x45b50c;
public override int ShopKabu => GSaveShop + 0x2d40; // part of shop; tailor & zakka increased size
public override int Museum => GSaveLandStart + 0x45ec44;
public override int Visitor => GSaveLandStart + 0x462048;
public override int SaveFg => GSaveLandStart + 0x462278;
public override int BulletinBoard => GSaveLandStart + 0x462bc0;
public override int AirportThemeColor => GSaveLandStart + 0x5437c8;
#endregion
public const int GSaveShop = GSaveLandStart + 0x45b50c;
public override int ShopKabu => GSaveShop + 0x2d40; // part of shop; tailor & zakka increased size
public override int Museum => GSaveLandStart + 0x45ec44;
public override int Visitor => GSaveLandStart + 0x462048;
public override int SaveFg => GSaveLandStart + 0x462278;
public override int BulletinBoard => GSaveLandStart + 0x462bc0;
public override int AirportThemeColor => GSaveLandStart + 0x5437c8;
#endregion
#region GSaveLandOther
public const int GSaveLandOtherStart = 0x547520;
#region GSaveLandOther
public const int GSaveLandOtherStart = 0x547520;
public override int LostItemBox => GSaveLandOtherStart + 0x3726c0;
public override int LastSavedTime => GSaveLandOtherStart + 0x377020;
#endregion
public override int LostItemBox => GSaveLandOtherStart + 0x3726c0;
public override int LastSavedTime => GSaveLandOtherStart + 0x377020;
#endregion
public override int VillagerSize => Villager2.SIZE;
public override IVillager ReadVillager(byte[] data) => new Villager2(data);
public override int VillagerSize => Villager2.SIZE;
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 int VillagerHouseSize => VillagerHouse2.SIZE;
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 int PlayerRoomSize => PlayerRoom2.SIZE;
public override IPlayerRoom ReadPlayerRoom(byte[] data) => new PlayerRoom2(data);
}
}
public override int PlayerHouseSize => PlayerHouse2.SIZE;
public override IPlayerHouse ReadPlayerHouse(Memory<byte> data) => new PlayerHouse2(data);
public override int PlayerRoomSize => PlayerRoom2.SIZE;
public override IPlayerRoom ReadPlayerRoom(Memory<byte> data) => new PlayerRoom2(data);
}

View File

@ -1,85 +1,84 @@
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; }
public abstract int Wallet { get; }
public abstract int NowPoint { get; }
public abstract int TotalPoint { get; }
public abstract int Birthday { get; }
public abstract int ProfileMain { get; }
public abstract int ProfilePhoto { get; }
public abstract int ProfileBirthday { get; }
public abstract int ProfileFruit { get; }
public abstract int ProfileTimestamp { get; }
public abstract int ProfileIsMakeVillage { get; }
public abstract int Pockets1 { get; }
public abstract int Pockets2 { get; }
public abstract int ItemChest { get; }
public abstract int ItemCollectBit { get; }
public abstract int ItemRemakeCollectBit { get; }
public abstract int Manpu { get; } // reactions
public abstract int Bank { get; }
public abstract int Recipes { get; }
public int MaxAchievementID { get; } = 512;
public int Pockets1Count { get; } = 20;
public int Pockets2Count { get; } = 20;
public int ItemChestCount { get; } = 5000;
public abstract int MaxRecipeID { get; }
public abstract int MaxRemakeBitFlag { get; }
public static PersonalOffsets GetOffsets(FileHeaderInfo Info)
{
public abstract int PersonalId { get; }
public abstract int EventFlagsPlayer { get; }
public abstract int CountAchievement { get; }
public abstract int Wallet { get; }
public abstract int NowPoint { get; }
public abstract int TotalPoint { get; }
public abstract int Birthday { get; }
public abstract int ProfileMain { get; }
public abstract int ProfilePhoto { get; }
public abstract int ProfileBirthday { get; }
public abstract int ProfileFruit { get; }
public abstract int ProfileTimestamp { get; }
public abstract int ProfileIsMakeVillage { get; }
public abstract int Pockets1 { get; }
public abstract int Pockets2 { get; }
public abstract int ItemChest { get; }
public abstract int ItemCollectBit { get; }
public abstract int ItemRemakeCollectBit { get; }
public abstract int Manpu { get; } // reactions
public abstract int Bank { get; }
public abstract int Recipes { get; }
public int MaxAchievementID { get; } = 512;
public int Pockets1Count { get; } = 20;
public int Pockets2Count { get; } = 20;
public int ItemChestCount { get; } = 5000;
public abstract int MaxRecipeID { get; }
public abstract int MaxRemakeBitFlag { get; }
public static PersonalOffsets GetOffsets(FileHeaderInfo Info)
var rev = Info.GetKnownRevisionIndex();
return rev switch
{
var rev = Info.GetKnownRevisionIndex();
return rev switch
{
0 => new PersonalOffsets10(),
1 => new PersonalOffsets11(),
2 => new PersonalOffsets11(),
3 => new PersonalOffsets11(),
4 => new PersonalOffsets11(),
5 => new PersonalOffsets11(),
6 => new PersonalOffsets12(),
7 => new PersonalOffsets12(),
8 => new PersonalOffsets13(),
9 => new PersonalOffsets13(),
10 => new PersonalOffsets14(),
11 => new PersonalOffsets14(),
12 => new PersonalOffsets14(),
13 => new PersonalOffsets15(),
14 => new PersonalOffsets15(),
15 => new PersonalOffsets16(),
16 => new PersonalOffsets17(),
17 => new PersonalOffsets18(),
18 => new PersonalOffsets19(),
19 => new PersonalOffsets110(),
20 => new PersonalOffsets111(),
21 => new PersonalOffsets111(),
22 => new PersonalOffsets20(),
23 => new PersonalOffsets20(),
24 => new PersonalOffsets20(),
25 => new PersonalOffsets20(),
26 => new PersonalOffsets20(),
27 => new PersonalOffsets20(),
28 => new PersonalOffsets20(),
29 => new PersonalOffsets20(),
30 => new PersonalOffsets20(),
_ => throw new IndexOutOfRangeException("Unknown revision!" + Environment.NewLine + Info),
};
}
public abstract IReactionStore ReadReactions(byte[] data);
public abstract void SetReactions(byte[] data, IReactionStore value);
0 => new PersonalOffsets10(),
1 => new PersonalOffsets11(),
2 => new PersonalOffsets11(),
3 => new PersonalOffsets11(),
4 => new PersonalOffsets11(),
5 => new PersonalOffsets11(),
6 => new PersonalOffsets12(),
7 => new PersonalOffsets12(),
8 => new PersonalOffsets13(),
9 => new PersonalOffsets13(),
10 => new PersonalOffsets14(),
11 => new PersonalOffsets14(),
12 => new PersonalOffsets14(),
13 => new PersonalOffsets15(),
14 => new PersonalOffsets15(),
15 => new PersonalOffsets16(),
16 => new PersonalOffsets17(),
17 => new PersonalOffsets18(),
18 => new PersonalOffsets19(),
19 => new PersonalOffsets110(),
20 => new PersonalOffsets111(),
21 => new PersonalOffsets111(),
22 => new PersonalOffsets20(),
23 => new PersonalOffsets20(),
24 => new PersonalOffsets20(),
25 => new PersonalOffsets20(),
26 => new PersonalOffsets20(),
27 => new PersonalOffsets20(),
28 => new PersonalOffsets20(),
29 => new PersonalOffsets20(),
30 => new PersonalOffsets20(),
_ => throw new IndexOutOfRangeException("Unknown revision!" + Environment.NewLine + Info),
};
}
public abstract IReactionStore ReadReactions(ReadOnlySpan<byte> data);
public abstract void SetReactions(Span<byte> data, IReactionStore value);
}

View File

@ -1,47 +1,48 @@
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;
private const int Player = 0x108;
public override int PersonalId => Player + 0xAF98;
public override int EventFlagsPlayer => Player + 0xAFD0;
public override int PersonalId => Player + 0xAF98;
public override int EventFlagsPlayer => Player + 0xAFD0;
private const int GSaveLifeSupport = Player + 0xBFD0;
public override int CountAchievement => GSaveLifeSupport + 0xE98; // CountAchievement
private const int GSaveLifeSupport = Player + 0xBFD0;
public override int CountAchievement => GSaveLifeSupport + 0xE98; // CountAchievement
public override int NowPoint => GSaveLifeSupport + 0x5498; // Nook Miles
public override int TotalPoint => NowPoint + 8; // Total Nook Miles Earned
public override int Birthday => Player + 0x11478;
public override int NowPoint => GSaveLifeSupport + 0x5498; // Nook Miles
public override int TotalPoint => NowPoint + 8; // Total Nook Miles Earned
public override int Birthday => Player + 0x11478;
public override int ProfileMain => Player + 0x11488;
public override int ProfilePhoto => ProfileMain + 0x14;
public override int ProfileBirthday => ProfileMain + 0x23040;
public override int ProfileFruit => ProfileMain + 0x23044;
public override int ProfileTimestamp => ProfileMain + 0x230B4;
public override int ProfileIsMakeVillage => ProfileMain + 0x230B8;
public override int ProfileMain => Player + 0x11488;
public override int ProfilePhoto => ProfileMain + 0x14;
public override int ProfileBirthday => ProfileMain + 0x23040;
public override int ProfileFruit => ProfileMain + 0x23044;
public override int ProfileTimestamp => ProfileMain + 0x230B4;
public override int ProfileIsMakeVillage => ProfileMain + 0x230B8;
// end player
// end player
private const int PlayerOther = 0x35BD0;
private const int PlayerOther = 0x35BD0;
public override int Pockets1 => PlayerOther + 0x4;
public override int Pockets2 => Pockets1 + (8 * Pockets1Count) + 0x18;
public override int Wallet => Pockets2 + (8 * Pockets2Count) + 0x18;
public override int ItemChest => PlayerOther + 0x180;
public override int ItemCollectBit => PlayerOther + 0xA04C;
public override int ItemRemakeCollectBit => PlayerOther + 0xA7A0;
public override int Manpu => PlayerOther + 0xAF70;
public override int Bank => PlayerOther + 0x33014;
public override int Recipes => Bank + 0x10;
public override int Pockets1 => PlayerOther + 0x4;
public override int Pockets2 => Pockets1 + (8 * Pockets1Count) + 0x18;
public override int Wallet => Pockets2 + (8 * Pockets2Count) + 0x18;
public override int ItemChest => PlayerOther + 0x180;
public override int ItemCollectBit => PlayerOther + 0xA04C;
public override int ItemRemakeCollectBit => PlayerOther + 0xA7A0;
public override int Manpu => PlayerOther + 0xAF70;
public override int Bank => PlayerOther + 0x33014;
public override int Recipes => Bank + 0x10;
public override int MaxRecipeID => 0x2A0;
public override int MaxRemakeBitFlag => 0x754 * 32;
public override int MaxRecipeID => 0x2A0;
public override int MaxRemakeBitFlag => 0x754 * 32;
public override IReactionStore ReadReactions(byte[] data) => data.Slice(Manpu, GSavePlayerManpu.SIZE).ToStructure<GSavePlayerManpu>();
public override void SetReactions(byte[] data, IReactionStore value) => ((GSavePlayerManpu)value).ToBytes().CopyTo(data, Manpu);
}
}
public override IReactionStore ReadReactions(ReadOnlySpan<byte> data) => data.Slice(Manpu, GSavePlayerManpu.SIZE).ToStructure<GSavePlayerManpu>();
public override void SetReactions(Span<byte> data, IReactionStore value) => ((GSavePlayerManpu)value).ToBytes().CopyTo(data[Manpu..]);
}

View File

@ -1,47 +1,48 @@
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;
private const int Player = 0x110;
public override int PersonalId => Player + 0xAFA8;
public override int EventFlagsPlayer => Player + 0xAFE0;
public override int PersonalId => Player + 0xAFA8;
public override int EventFlagsPlayer => Player + 0xAFE0;
private const int GSaveLifeSupport = Player + 0xBFE0;
public override int CountAchievement => GSaveLifeSupport + 0xE98;
private const int GSaveLifeSupport = Player + 0xBFE0;
public override int CountAchievement => GSaveLifeSupport + 0xE98;
public override int NowPoint => GSaveLifeSupport + 0x5498; // Nook Miles
public override int TotalPoint => NowPoint + 8; // Total Nook Miles Earned
public override int Birthday => Player + 0x11488;
public override int NowPoint => GSaveLifeSupport + 0x5498; // Nook Miles
public override int TotalPoint => NowPoint + 8; // Total Nook Miles Earned
public override int Birthday => Player + 0x11488;
public override int ProfileMain => Player + 0x114A0;
public override int ProfilePhoto => ProfileMain + 0x14;
public override int ProfileBirthday => ProfileMain + 0x23058;
public override int ProfileFruit => ProfileMain + 0x2305C;
public override int ProfileTimestamp => ProfileMain + 0x230CC;
public override int ProfileIsMakeVillage => ProfileMain + 0x230D0;
public override int ProfileMain => Player + 0x114A0;
public override int ProfilePhoto => ProfileMain + 0x14;
public override int ProfileBirthday => ProfileMain + 0x23058;
public override int ProfileFruit => ProfileMain + 0x2305C;
public override int ProfileTimestamp => ProfileMain + 0x230CC;
public override int ProfileIsMakeVillage => ProfileMain + 0x230D0;
// end player
// end player
private const int PlayerOther = 0x35C10;
private const int PlayerOther = 0x35C10;
public override int Pockets1 => PlayerOther + 0x10;
public override int Pockets2 => Pockets1 + (8 * Pockets1Count) + 0x18;
public override int Wallet => Pockets2 + (8 * Pockets2Count) + 0x18;
public override int ItemChest => PlayerOther + 0x18C;
public override int ItemCollectBit => PlayerOther + 0xA058;
public override int ItemRemakeCollectBit => PlayerOther + 0xA7AC;
public override int Manpu => PlayerOther + 0xAF7C;
public override int Bank => PlayerOther + 0x33024;
public override int Recipes => Bank + 0x10;
public override int Pockets1 => PlayerOther + 0x10;
public override int Pockets2 => Pockets1 + (8 * Pockets1Count) + 0x18;
public override int Wallet => Pockets2 + (8 * Pockets2Count) + 0x18;
public override int ItemChest => PlayerOther + 0x18C;
public override int ItemCollectBit => PlayerOther + 0xA058;
public override int ItemRemakeCollectBit => PlayerOther + 0xA7AC;
public override int Manpu => PlayerOther + 0xAF7C;
public override int Bank => PlayerOther + 0x33024;
public override int Recipes => Bank + 0x10;
public override int MaxRecipeID => 0x2C8;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override int MaxRecipeID => 0x2C8;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override IReactionStore ReadReactions(byte[] data) => data.Slice(Manpu, GSavePlayerManpu.SIZE).ToStructure<GSavePlayerManpu>();
public override void SetReactions(byte[] data, IReactionStore value) => ((GSavePlayerManpu)value).ToBytes().CopyTo(data, Manpu);
}
}
public override IReactionStore ReadReactions(ReadOnlySpan<byte> data) => data.Slice(Manpu, GSavePlayerManpu.SIZE).ToStructure<GSavePlayerManpu>();
public override void SetReactions(Span<byte> data, IReactionStore value) => ((GSavePlayerManpu)value).ToBytes().CopyTo(data[Manpu..]);
}

View File

@ -1,48 +1,49 @@
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;
private const int Player = 0x110;
public override int PersonalId => Player + 0xAFA8;
public override int EventFlagsPlayer => Player + 0xAFE0;
public override int PersonalId => Player + 0xAFA8;
public override int EventFlagsPlayer => Player + 0xAFE0;
private const int GSaveLifeSupport = Player + 0xBFE0;
public override int CountAchievement => GSaveLifeSupport + 0xE98; // CountAchievement
private const int GSaveLifeSupport = Player + 0xBFE0;
public override int CountAchievement => GSaveLifeSupport + 0xE98; // CountAchievement
public override int NowPoint => GSaveLifeSupport + 0x5498; // Nook Miles
public override int TotalPoint => NowPoint + 8; // Total Nook Miles Earned
public override int Birthday => Player + 0x1228c;
public override int NowPoint => GSaveLifeSupport + 0x5498; // Nook Miles
public override int TotalPoint => NowPoint + 8; // Total Nook Miles Earned
public override int Birthday => Player + 0x1228c;
public override int ProfileMain => Player + 0x122a0;
public override int ProfilePhoto => ProfileMain + 0x14;
public override int ProfileBirthday => ProfileMain + 0x23058;
public override int ProfileFruit => ProfileMain + 0x2305C;
public override int ProfileTimestamp => ProfileMain + 0x230CC;
public override int ProfileIsMakeVillage => ProfileMain + 0x230D0;
public override int ProfileMain => Player + 0x122a0;
public override int ProfilePhoto => ProfileMain + 0x14;
public override int ProfileBirthday => ProfileMain + 0x23058;
public override int ProfileFruit => ProfileMain + 0x2305C;
public override int ProfileTimestamp => ProfileMain + 0x230CC;
public override int ProfileIsMakeVillage => ProfileMain + 0x230D0;
// end player
// end player
private const int PlayerOther = 0x36a50;
private const int PlayerOther = 0x36a50;
public override int Pockets1 => PlayerOther + 0x10;
public override int Pockets2 => Pockets1 + (8 * Pockets1Count) + 0x18;
public override int Wallet => Pockets2 + (8 * Pockets2Count) + 0x18;
public override int ItemChest => PlayerOther + 0x18C;
public override int ItemCollectBit => PlayerOther + 0xA058;
public override int ItemRemakeCollectBit => PlayerOther + 0xA7AC;
public override int Manpu => PlayerOther + 0xAF7C;
public override int Bank => PlayerOther + 0x22594;
public override int Recipes => Bank + 0x10;
public override int Pockets1 => PlayerOther + 0x10;
public override int Pockets2 => Pockets1 + (8 * Pockets1Count) + 0x18;
public override int Wallet => Pockets2 + (8 * Pockets2Count) + 0x18;
public override int ItemChest => PlayerOther + 0x18C;
public override int ItemCollectBit => PlayerOther + 0xA058;
public override int ItemRemakeCollectBit => PlayerOther + 0xA7AC;
public override int Manpu => PlayerOther + 0xAF7C;
public override int Bank => PlayerOther + 0x22594;
public override int Recipes => Bank + 0x10;
public override int MaxRecipeID => 0x308;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override int MaxRecipeID => 0x308;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override IReactionStore ReadReactions(byte[] data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(byte[] data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data, Manpu);
}
}
public override IReactionStore ReadReactions(ReadOnlySpan<byte> data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(Span<byte> data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data[Manpu..]);
}

View File

@ -1,48 +1,49 @@
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;
private const int Player = 0x110;
public override int PersonalId => Player + 0xAFA8;
public override int EventFlagsPlayer => Player + 0xAFE0;
public override int PersonalId => Player + 0xAFA8;
public override int EventFlagsPlayer => Player + 0xAFE0;
private const int GSaveLifeSupport = Player + 0xBFE0;
public override int CountAchievement => GSaveLifeSupport + 0xE98; // CountAchievement
private const int GSaveLifeSupport = Player + 0xBFE0;
public override int CountAchievement => GSaveLifeSupport + 0xE98; // CountAchievement
public override int NowPoint => GSaveLifeSupport + 0x5498; // Nook Miles
public override int TotalPoint => NowPoint + 8; // Total Nook Miles Earned
public override int Birthday => Player + 0x1228c;
public override int NowPoint => GSaveLifeSupport + 0x5498; // Nook Miles
public override int TotalPoint => NowPoint + 8; // Total Nook Miles Earned
public override int Birthday => Player + 0x1228c;
public override int ProfileMain => Player + 0x122a0;
public override int ProfilePhoto => ProfileMain + 0x14;
public override int ProfileBirthday => ProfileMain + 0x23058;
public override int ProfileFruit => ProfileMain + 0x2305C;
public override int ProfileTimestamp => ProfileMain + 0x230CC;
public override int ProfileIsMakeVillage => ProfileMain + 0x230D0;
public override int ProfileMain => Player + 0x122a0;
public override int ProfilePhoto => ProfileMain + 0x14;
public override int ProfileBirthday => ProfileMain + 0x23058;
public override int ProfileFruit => ProfileMain + 0x2305C;
public override int ProfileTimestamp => ProfileMain + 0x230CC;
public override int ProfileIsMakeVillage => ProfileMain + 0x230D0;
// end player
// end player
private const int PlayerOther = 0x36a50;
private const int PlayerOther = 0x36a50;
public override int Pockets1 => PlayerOther + 0x10;
public override int Pockets2 => Pockets1 + (8 * Pockets1Count) + 0x18;
public override int Wallet => Pockets2 + (8 * Pockets2Count) + 0x18;
public override int ItemChest => PlayerOther + 0x18C;
public override int ItemCollectBit => PlayerOther + 0xA058;
public override int ItemRemakeCollectBit => PlayerOther + 0xA7AC;
public override int Manpu => PlayerOther + 0xAF7C;
public override int Bank => PlayerOther + 0x22594;
public override int Recipes => Bank + 0x10;
public override int Pockets1 => PlayerOther + 0x10;
public override int Pockets2 => Pockets1 + (8 * Pockets1Count) + 0x18;
public override int Wallet => Pockets2 + (8 * Pockets2Count) + 0x18;
public override int ItemChest => PlayerOther + 0x18C;
public override int ItemCollectBit => PlayerOther + 0xA058;
public override int ItemRemakeCollectBit => PlayerOther + 0xA7AC;
public override int Manpu => PlayerOther + 0xAF7C;
public override int Bank => PlayerOther + 0x22594;
public override int Recipes => Bank + 0x10;
public override int MaxRecipeID => 0x3D6;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override int MaxRecipeID => 0x3D6;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override IReactionStore ReadReactions(byte[] data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(byte[] data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data, Manpu);
}
public override IReactionStore ReadReactions(ReadOnlySpan<byte> data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(Span<byte> data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data[Manpu..]);
}

View File

@ -1,47 +1,48 @@
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;
private const int Player = 0x110;
public override int PersonalId => Player + 0xAFA8;
public override int EventFlagsPlayer => Player + 0xAFE0;
public override int PersonalId => Player + 0xAFA8;
public override int EventFlagsPlayer => Player + 0xAFE0;
private const int GSaveLifeSupport = Player + 0xBFE0;
public override int CountAchievement => GSaveLifeSupport + 0xE98; // CountAchievement
private const int GSaveLifeSupport = Player + 0xBFE0;
public override int CountAchievement => GSaveLifeSupport + 0xE98; // CountAchievement
public override int NowPoint => GSaveLifeSupport + 0x5498; // Nook Miles
public override int TotalPoint => NowPoint + 8; // Total Nook Miles Earned
public override int Birthday => Player + 0x1168C;
public override int NowPoint => GSaveLifeSupport + 0x5498; // Nook Miles
public override int TotalPoint => NowPoint + 8; // Total Nook Miles Earned
public override int Birthday => Player + 0x1168C;
public override int ProfileMain => Player + 0x116A0;
public override int ProfilePhoto => ProfileMain + 0x14;
public override int ProfileBirthday => ProfileMain + 0x23058;
public override int ProfileFruit => ProfileMain + 0x2305C;
public override int ProfileTimestamp => ProfileMain + 0x230CC;
public override int ProfileIsMakeVillage => ProfileMain + 0x230D0;
public override int ProfileMain => Player + 0x116A0;
public override int ProfilePhoto => ProfileMain + 0x14;
public override int ProfileBirthday => ProfileMain + 0x23058;
public override int ProfileFruit => ProfileMain + 0x2305C;
public override int ProfileTimestamp => ProfileMain + 0x230CC;
public override int ProfileIsMakeVillage => ProfileMain + 0x230D0;
// end player
// end player
private const int PlayerOther = 0x35E40;
private const int PlayerOther = 0x35E40;
public override int Pockets1 => PlayerOther + 0x10;
public override int Pockets2 => Pockets1 + (8 * Pockets1Count) + 0x18;
public override int Wallet => Pockets2 + (8 * Pockets2Count) + 0x18;
public override int ItemChest => PlayerOther + 0x18C;
public override int ItemCollectBit => PlayerOther + 0xA058;
public override int ItemRemakeCollectBit => PlayerOther + 0xA7AC;
public override int Manpu => PlayerOther + 0xAF7C;
public override int Bank => PlayerOther + 0x345E4;
public override int Recipes => Bank + 0x10;
public override int Pockets1 => PlayerOther + 0x10;
public override int Pockets2 => Pockets1 + (8 * Pockets1Count) + 0x18;
public override int Wallet => Pockets2 + (8 * Pockets2Count) + 0x18;
public override int ItemChest => PlayerOther + 0x18C;
public override int ItemCollectBit => PlayerOther + 0xA058;
public override int ItemRemakeCollectBit => PlayerOther + 0xA7AC;
public override int Manpu => PlayerOther + 0xAF7C;
public override int Bank => PlayerOther + 0x345E4;
public override int Recipes => Bank + 0x10;
public override int MaxRecipeID => 0x2DA; // mermaid stuff
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override int MaxRecipeID => 0x2DA; // mermaid stuff
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override IReactionStore ReadReactions(byte[] data) => data.Slice(Manpu, GSavePlayerManpu.SIZE).ToStructure<GSavePlayerManpu>();
public override void SetReactions(byte[] data, IReactionStore value) => ((GSavePlayerManpu)value).ToBytes().CopyTo(data, Manpu);
}
}
public override IReactionStore ReadReactions(ReadOnlySpan<byte> data) => data.Slice(Manpu, GSavePlayerManpu.SIZE).ToStructure<GSavePlayerManpu>();
public override void SetReactions(Span<byte> data, IReactionStore value) => ((GSavePlayerManpu)value).ToBytes().CopyTo(data[Manpu..]);
}

View File

@ -1,47 +1,48 @@
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;
private const int Player = 0x110;
public override int PersonalId => Player + 0xAFA8;
public override int EventFlagsPlayer => Player + 0xAFE0;
public override int PersonalId => Player + 0xAFA8;
public override int EventFlagsPlayer => Player + 0xAFE0;
private const int GSaveLifeSupport = Player + 0xBFE0;
public override int CountAchievement => GSaveLifeSupport + 0xE98; // CountAchievement
private const int GSaveLifeSupport = Player + 0xBFE0;
public override int CountAchievement => GSaveLifeSupport + 0xE98; // CountAchievement
public override int NowPoint => GSaveLifeSupport + 0x5498; // Nook Miles
public override int TotalPoint => NowPoint + 8; // Total Nook Miles Earned
public override int Birthday => Player + 0x1168C;
public override int NowPoint => GSaveLifeSupport + 0x5498; // Nook Miles
public override int TotalPoint => NowPoint + 8; // Total Nook Miles Earned
public override int Birthday => Player + 0x1168C;
public override int ProfileMain => Player + 0x116A0;
public override int ProfilePhoto => ProfileMain + 0x14;
public override int ProfileBirthday => ProfileMain + 0x23058;
public override int ProfileFruit => ProfileMain + 0x2305C;
public override int ProfileTimestamp => ProfileMain + 0x230CC;
public override int ProfileIsMakeVillage => ProfileMain + 0x230D0;
public override int ProfileMain => Player + 0x116A0;
public override int ProfilePhoto => ProfileMain + 0x14;
public override int ProfileBirthday => ProfileMain + 0x23058;
public override int ProfileFruit => ProfileMain + 0x2305C;
public override int ProfileTimestamp => ProfileMain + 0x230CC;
public override int ProfileIsMakeVillage => ProfileMain + 0x230D0;
// end player
// end player
private const int PlayerOther = 0x35E40;
private const int PlayerOther = 0x35E40;
public override int Pockets1 => PlayerOther + 0x10;
public override int Pockets2 => Pockets1 + (8 * Pockets1Count) + 0x18;
public override int Wallet => Pockets2 + (8 * Pockets2Count) + 0x18;
public override int ItemChest => PlayerOther + 0x18C;
public override int ItemCollectBit => PlayerOther + 0xA058;
public override int ItemRemakeCollectBit => PlayerOther + 0xA7AC;
public override int Manpu => PlayerOther + 0xAF7C;
public override int Bank => PlayerOther + 0x345E4;
public override int Recipes => Bank + 0x10;
public override int Pockets1 => PlayerOther + 0x10;
public override int Pockets2 => Pockets1 + (8 * Pockets1Count) + 0x18;
public override int Wallet => Pockets2 + (8 * Pockets2Count) + 0x18;
public override int ItemChest => PlayerOther + 0x18C;
public override int ItemCollectBit => PlayerOther + 0xA058;
public override int ItemRemakeCollectBit => PlayerOther + 0xA7AC;
public override int Manpu => PlayerOther + 0xAF7C;
public override int Bank => PlayerOther + 0x345E4;
public override int Recipes => Bank + 0x10;
public override int MaxRecipeID => 0x2DA;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override int MaxRecipeID => 0x2DA;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override IReactionStore ReadReactions(byte[] data) => data.Slice(Manpu, GSavePlayerManpu.SIZE).ToStructure<GSavePlayerManpu>();
public override void SetReactions(byte[] data, IReactionStore value) => ((GSavePlayerManpu)value).ToBytes().CopyTo(data, Manpu);
}
}
public override IReactionStore ReadReactions(ReadOnlySpan<byte> data) => data.Slice(Manpu, GSavePlayerManpu.SIZE).ToStructure<GSavePlayerManpu>();
public override void SetReactions(Span<byte> data, IReactionStore value) => ((GSavePlayerManpu)value).ToBytes().CopyTo(data[Manpu..]);
}

View File

@ -1,47 +1,48 @@
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;
private const int Player = 0x110;
public override int PersonalId => Player + 0xAFA8;
public override int EventFlagsPlayer => Player + 0xAFE0;
public override int PersonalId => Player + 0xAFA8;
public override int EventFlagsPlayer => Player + 0xAFE0;
private const int GSaveLifeSupport = Player + 0xBFE0;
public override int CountAchievement => GSaveLifeSupport + 0xE98; // CountAchievement
private const int GSaveLifeSupport = Player + 0xBFE0;
public override int CountAchievement => GSaveLifeSupport + 0xE98; // CountAchievement
public override int NowPoint => GSaveLifeSupport + 0x5498; // Nook Miles
public override int TotalPoint => NowPoint + 8; // Total Nook Miles Earned
public override int Birthday => Player + 0x1168C;
public override int NowPoint => GSaveLifeSupport + 0x5498; // Nook Miles
public override int TotalPoint => NowPoint + 8; // Total Nook Miles Earned
public override int Birthday => Player + 0x1168C;
public override int ProfileMain => Player + 0x116A0;
public override int ProfilePhoto => ProfileMain + 0x14;
public override int ProfileBirthday => ProfileMain + 0x23058;
public override int ProfileFruit => ProfileMain + 0x2305C;
public override int ProfileTimestamp => ProfileMain + 0x230CC;
public override int ProfileIsMakeVillage => ProfileMain + 0x230D0;
public override int ProfileMain => Player + 0x116A0;
public override int ProfilePhoto => ProfileMain + 0x14;
public override int ProfileBirthday => ProfileMain + 0x23058;
public override int ProfileFruit => ProfileMain + 0x2305C;
public override int ProfileTimestamp => ProfileMain + 0x230CC;
public override int ProfileIsMakeVillage => ProfileMain + 0x230D0;
// end player
// end player
private const int PlayerOther = 0x35E40;
private const int PlayerOther = 0x35E40;
public override int Pockets1 => PlayerOther + 0x10;
public override int Pockets2 => Pockets1 + (8 * Pockets1Count) + 0x18;
public override int Wallet => Pockets2 + (8 * Pockets2Count) + 0x18;
public override int ItemChest => PlayerOther + 0x18C;
public override int ItemCollectBit => PlayerOther + 0xA058;
public override int ItemRemakeCollectBit => PlayerOther + 0xA7AC;
public override int Manpu => PlayerOther + 0xAF7C;
public override int Bank => PlayerOther + 0x350C4;
public override int Recipes => Bank + 0x10;
public override int Pockets1 => PlayerOther + 0x10;
public override int Pockets2 => Pockets1 + (8 * Pockets1Count) + 0x18;
public override int Wallet => Pockets2 + (8 * Pockets2Count) + 0x18;
public override int ItemChest => PlayerOther + 0x18C;
public override int ItemCollectBit => PlayerOther + 0xA058;
public override int ItemRemakeCollectBit => PlayerOther + 0xA7AC;
public override int Manpu => PlayerOther + 0xAF7C;
public override int Bank => PlayerOther + 0x350C4;
public override int Recipes => Bank + 0x10;
public override int MaxRecipeID => 0x2DA;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override int MaxRecipeID => 0x2DA;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override IReactionStore ReadReactions(byte[] data) => data.Slice(Manpu, GSavePlayerManpu.SIZE).ToStructure<GSavePlayerManpu>();
public override void SetReactions(byte[] data, IReactionStore value) => ((GSavePlayerManpu)value).ToBytes().CopyTo(data, Manpu);
}
}
public override IReactionStore ReadReactions(ReadOnlySpan<byte> data) => data.Slice(Manpu, GSavePlayerManpu.SIZE).ToStructure<GSavePlayerManpu>();
public override void SetReactions(Span<byte> data, IReactionStore value) => ((GSavePlayerManpu)value).ToBytes().CopyTo(data[Manpu..]);
}

View File

@ -1,47 +1,48 @@
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;
private const int Player = 0x110;
public override int PersonalId => Player + 0xAFA8;
public override int EventFlagsPlayer => Player + 0xAFE0;
public override int PersonalId => Player + 0xAFA8;
public override int EventFlagsPlayer => Player + 0xAFE0;
private const int GSaveLifeSupport = Player + 0xBFE0;
public override int CountAchievement => GSaveLifeSupport + 0xE98; // CountAchievement
private const int GSaveLifeSupport = Player + 0xBFE0;
public override int CountAchievement => GSaveLifeSupport + 0xE98; // CountAchievement
public override int NowPoint => GSaveLifeSupport + 0x5498; // Nook Miles
public override int TotalPoint => NowPoint + 8; // Total Nook Miles Earned
public override int Birthday => Player + 0x1228c;
public override int NowPoint => GSaveLifeSupport + 0x5498; // Nook Miles
public override int TotalPoint => NowPoint + 8; // Total Nook Miles Earned
public override int Birthday => Player + 0x1228c;
public override int ProfileMain => Player + 0x122a0;
public override int ProfilePhoto => ProfileMain + 0x14;
public override int ProfileBirthday => ProfileMain + 0x23058;
public override int ProfileFruit => ProfileMain + 0x2305C;
public override int ProfileTimestamp => ProfileMain + 0x230CC;
public override int ProfileIsMakeVillage => ProfileMain + 0x230D0;
public override int ProfileMain => Player + 0x122a0;
public override int ProfilePhoto => ProfileMain + 0x14;
public override int ProfileBirthday => ProfileMain + 0x23058;
public override int ProfileFruit => ProfileMain + 0x2305C;
public override int ProfileTimestamp => ProfileMain + 0x230CC;
public override int ProfileIsMakeVillage => ProfileMain + 0x230D0;
// end player
// end player
private const int PlayerOther = 0x36a50;
private const int PlayerOther = 0x36a50;
public override int Pockets1 => PlayerOther + 0x10;
public override int Pockets2 => Pockets1 + (8 * Pockets1Count) + 0x18;
public override int Wallet => Pockets2 + (8 * Pockets2Count) + 0x18;
public override int ItemChest => PlayerOther + 0x18C;
public override int ItemCollectBit => PlayerOther + 0xA058;
public override int ItemRemakeCollectBit => PlayerOther + 0xA7AC;
public override int Manpu => PlayerOther + 0xAF7C;
public override int Bank => PlayerOther + 0x35244;
public override int Recipes => Bank + 0x10;
public override int Pockets1 => PlayerOther + 0x10;
public override int Pockets2 => Pockets1 + (8 * Pockets1Count) + 0x18;
public override int Wallet => Pockets2 + (8 * Pockets2Count) + 0x18;
public override int ItemChest => PlayerOther + 0x18C;
public override int ItemCollectBit => PlayerOther + 0xA058;
public override int ItemRemakeCollectBit => PlayerOther + 0xA7AC;
public override int Manpu => PlayerOther + 0xAF7C;
public override int Bank => PlayerOther + 0x35244;
public override int Recipes => Bank + 0x10;
public override int MaxRecipeID => 0x2E1;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override int MaxRecipeID => 0x2E1;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override IReactionStore ReadReactions(byte[] data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(byte[] data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data, Manpu);
}
}
public override IReactionStore ReadReactions(ReadOnlySpan<byte> data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(Span<byte> data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data[Manpu..]);
}

View File

@ -1,47 +1,48 @@
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;
private const int Player = 0x110;
public override int PersonalId => Player + 0xAFA8;
public override int EventFlagsPlayer => Player + 0xAFE0;
public override int PersonalId => Player + 0xAFA8;
public override int EventFlagsPlayer => Player + 0xAFE0;
private const int GSaveLifeSupport = Player + 0xBFE0;
public override int CountAchievement => GSaveLifeSupport + 0xE98; // CountAchievement
private const int GSaveLifeSupport = Player + 0xBFE0;
public override int CountAchievement => GSaveLifeSupport + 0xE98; // CountAchievement
public override int NowPoint => GSaveLifeSupport + 0x5498; // Nook Miles
public override int TotalPoint => NowPoint + 8; // Total Nook Miles Earned
public override int Birthday => Player + 0x1228c;
public override int NowPoint => GSaveLifeSupport + 0x5498; // Nook Miles
public override int TotalPoint => NowPoint + 8; // Total Nook Miles Earned
public override int Birthday => Player + 0x1228c;
public override int ProfileMain => Player + 0x122a0;
public override int ProfilePhoto => ProfileMain + 0x14;
public override int ProfileBirthday => ProfileMain + 0x23058;
public override int ProfileFruit => ProfileMain + 0x2305C;
public override int ProfileTimestamp => ProfileMain + 0x230CC;
public override int ProfileIsMakeVillage => ProfileMain + 0x230D0;
public override int ProfileMain => Player + 0x122a0;
public override int ProfilePhoto => ProfileMain + 0x14;
public override int ProfileBirthday => ProfileMain + 0x23058;
public override int ProfileFruit => ProfileMain + 0x2305C;
public override int ProfileTimestamp => ProfileMain + 0x230CC;
public override int ProfileIsMakeVillage => ProfileMain + 0x230D0;
// end player
// end player
private const int PlayerOther = 0x36a50;
private const int PlayerOther = 0x36a50;
public override int Pockets1 => PlayerOther + 0x10;
public override int Pockets2 => Pockets1 + (8 * Pockets1Count) + 0x18;
public override int Wallet => Pockets2 + (8 * Pockets2Count) + 0x18;
public override int ItemChest => PlayerOther + 0x18C;
public override int ItemCollectBit => PlayerOther + 0xA058;
public override int ItemRemakeCollectBit => PlayerOther + 0xA7AC;
public override int Manpu => PlayerOther + 0xAF7C;
public override int Bank => PlayerOther + 0x35244;
public override int Recipes => Bank + 0x10;
public override int Pockets1 => PlayerOther + 0x10;
public override int Pockets2 => Pockets1 + (8 * Pockets1Count) + 0x18;
public override int Wallet => Pockets2 + (8 * Pockets2Count) + 0x18;
public override int ItemChest => PlayerOther + 0x18C;
public override int ItemCollectBit => PlayerOther + 0xA058;
public override int ItemRemakeCollectBit => PlayerOther + 0xA7AC;
public override int Manpu => PlayerOther + 0xAF7C;
public override int Bank => PlayerOther + 0x35244;
public override int Recipes => Bank + 0x10;
public override int MaxRecipeID => 0x2E1;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override int MaxRecipeID => 0x2E1;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override IReactionStore ReadReactions(byte[] data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(byte[] data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data, Manpu);
}
}
public override IReactionStore ReadReactions(ReadOnlySpan<byte> data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(Span<byte> data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data[Manpu..]);
}

View File

@ -1,47 +1,48 @@
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;
private const int Player = 0x110;
public override int PersonalId => Player + 0xAFA8;
public override int EventFlagsPlayer => Player + 0xAFE0;
public override int PersonalId => Player + 0xAFA8;
public override int EventFlagsPlayer => Player + 0xAFE0;
private const int GSaveLifeSupport = Player + 0xBFE0;
public override int CountAchievement => GSaveLifeSupport + 0xE98; // CountAchievement
private const int GSaveLifeSupport = Player + 0xBFE0;
public override int CountAchievement => GSaveLifeSupport + 0xE98; // CountAchievement
public override int NowPoint => GSaveLifeSupport + 0x5498; // Nook Miles
public override int TotalPoint => NowPoint + 8; // Total Nook Miles Earned
public override int Birthday => Player + 0x1228c;
public override int NowPoint => GSaveLifeSupport + 0x5498; // Nook Miles
public override int TotalPoint => NowPoint + 8; // Total Nook Miles Earned
public override int Birthday => Player + 0x1228c;
public override int ProfileMain => Player + 0x122a0;
public override int ProfilePhoto => ProfileMain + 0x14;
public override int ProfileBirthday => ProfileMain + 0x23058;
public override int ProfileFruit => ProfileMain + 0x2305C;
public override int ProfileTimestamp => ProfileMain + 0x230CC;
public override int ProfileIsMakeVillage => ProfileMain + 0x230D0;
public override int ProfileMain => Player + 0x122a0;
public override int ProfilePhoto => ProfileMain + 0x14;
public override int ProfileBirthday => ProfileMain + 0x23058;
public override int ProfileFruit => ProfileMain + 0x2305C;
public override int ProfileTimestamp => ProfileMain + 0x230CC;
public override int ProfileIsMakeVillage => ProfileMain + 0x230D0;
// end player
// end player
private const int PlayerOther = 0x36a50;
private const int PlayerOther = 0x36a50;
public override int Pockets1 => PlayerOther + 0x10;
public override int Pockets2 => Pockets1 + (8 * Pockets1Count) + 0x18;
public override int Wallet => Pockets2 + (8 * Pockets2Count) + 0x18;
public override int ItemChest => PlayerOther + 0x18C;
public override int ItemCollectBit => PlayerOther + 0xA058;
public override int ItemRemakeCollectBit => PlayerOther + 0xA7AC;
public override int Manpu => PlayerOther + 0xAF7C;
public override int Bank => PlayerOther + 0x22594;
public override int Recipes => Bank + 0x10;
public override int Pockets1 => PlayerOther + 0x10;
public override int Pockets2 => Pockets1 + (8 * Pockets1Count) + 0x18;
public override int Wallet => Pockets2 + (8 * Pockets2Count) + 0x18;
public override int ItemChest => PlayerOther + 0x18C;
public override int ItemCollectBit => PlayerOther + 0xA058;
public override int ItemRemakeCollectBit => PlayerOther + 0xA7AC;
public override int Manpu => PlayerOther + 0xAF7C;
public override int Bank => PlayerOther + 0x22594;
public override int Recipes => Bank + 0x10;
public override int MaxRecipeID => 0x308;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override int MaxRecipeID => 0x308;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override IReactionStore ReadReactions(byte[] data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(byte[] data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data, Manpu);
}
}
public override IReactionStore ReadReactions(ReadOnlySpan<byte> data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(Span<byte> data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data[Manpu..]);
}

View File

@ -1,48 +1,49 @@
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;
private const int Player = 0x110;
public override int PersonalId => Player + 0xAFA8;
public override int EventFlagsPlayer => Player + 0xAFE0;
public override int PersonalId => Player + 0xAFA8;
public override int EventFlagsPlayer => Player + 0xAFE0;
private const int GSaveLifeSupport = Player + 0xBFE0;
public override int CountAchievement => GSaveLifeSupport + 0xE98; // CountAchievement
private const int GSaveLifeSupport = Player + 0xBFE0;
public override int CountAchievement => GSaveLifeSupport + 0xE98; // CountAchievement
public override int NowPoint => GSaveLifeSupport + 0x5498; // Nook Miles
public override int TotalPoint => NowPoint + 8; // Total Nook Miles Earned
public override int Birthday => Player + 0x1228c;
public override int NowPoint => GSaveLifeSupport + 0x5498; // Nook Miles
public override int TotalPoint => NowPoint + 8; // Total Nook Miles Earned
public override int Birthday => Player + 0x1228c;
public override int ProfileMain => Player + 0x122a0;
public override int ProfilePhoto => ProfileMain + 0x14;
public override int ProfileBirthday => ProfileMain + 0x23058;
public override int ProfileFruit => ProfileMain + 0x2305C;
public override int ProfileTimestamp => ProfileMain + 0x230CC;
public override int ProfileIsMakeVillage => ProfileMain + 0x230D0;
public override int ProfileMain => Player + 0x122a0;
public override int ProfilePhoto => ProfileMain + 0x14;
public override int ProfileBirthday => ProfileMain + 0x23058;
public override int ProfileFruit => ProfileMain + 0x2305C;
public override int ProfileTimestamp => ProfileMain + 0x230CC;
public override int ProfileIsMakeVillage => ProfileMain + 0x230D0;
// end player
// end player
private const int PlayerOther = 0x36a50;
private const int PlayerOther = 0x36a50;
public override int Pockets1 => PlayerOther + 0x10;
public override int Pockets2 => Pockets1 + (8 * Pockets1Count) + 0x18;
public override int Wallet => Pockets2 + (8 * Pockets2Count) + 0x18;
public override int ItemChest => PlayerOther + 0x18C;
public override int ItemCollectBit => PlayerOther + 0xA058;
public override int ItemRemakeCollectBit => PlayerOther + 0xA7AC;
public override int Manpu => PlayerOther + 0xAF7C;
public override int Bank => PlayerOther + 0x22594;
public override int Recipes => Bank + 0x10;
public override int Pockets1 => PlayerOther + 0x10;
public override int Pockets2 => Pockets1 + (8 * Pockets1Count) + 0x18;
public override int Wallet => Pockets2 + (8 * Pockets2Count) + 0x18;
public override int ItemChest => PlayerOther + 0x18C;
public override int ItemCollectBit => PlayerOther + 0xA058;
public override int ItemRemakeCollectBit => PlayerOther + 0xA7AC;
public override int Manpu => PlayerOther + 0xAF7C;
public override int Bank => PlayerOther + 0x22594;
public override int Recipes => Bank + 0x10;
public override int MaxRecipeID => 0x308;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override int MaxRecipeID => 0x308;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override IReactionStore ReadReactions(byte[] data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(byte[] data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data, Manpu);
}
}
public override IReactionStore ReadReactions(ReadOnlySpan<byte> data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(Span<byte> data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data[Manpu..]);
}

View File

@ -1,48 +1,49 @@
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;
private const int Player = 0x110;
public override int PersonalId => Player + 0xAFA8;
public override int EventFlagsPlayer => Player + 0xAFE0;
public override int PersonalId => Player + 0xAFA8;
public override int EventFlagsPlayer => Player + 0xAFE0;
private const int GSaveLifeSupport = Player + 0xBFE0;
public override int CountAchievement => GSaveLifeSupport + 0xE98; // CountAchievement
private const int GSaveLifeSupport = Player + 0xBFE0;
public override int CountAchievement => GSaveLifeSupport + 0xE98; // CountAchievement
public override int NowPoint => GSaveLifeSupport + 0x5498; // Nook Miles
public override int TotalPoint => NowPoint + 8; // Total Nook Miles Earned
public override int Birthday => Player + 0x1228c;
public override int NowPoint => GSaveLifeSupport + 0x5498; // Nook Miles
public override int TotalPoint => NowPoint + 8; // Total Nook Miles Earned
public override int Birthday => Player + 0x1228c;
public override int ProfileMain => Player + 0x122a0;
public override int ProfilePhoto => ProfileMain + 0x14;
public override int ProfileBirthday => ProfileMain + 0x23058;
public override int ProfileFruit => ProfileMain + 0x2305C;
public override int ProfileTimestamp => ProfileMain + 0x230CC;
public override int ProfileIsMakeVillage => ProfileMain + 0x230D0;
public override int ProfileMain => Player + 0x122a0;
public override int ProfilePhoto => ProfileMain + 0x14;
public override int ProfileBirthday => ProfileMain + 0x23058;
public override int ProfileFruit => ProfileMain + 0x2305C;
public override int ProfileTimestamp => ProfileMain + 0x230CC;
public override int ProfileIsMakeVillage => ProfileMain + 0x230D0;
// end player
// end player
private const int PlayerOther = 0x36a50;
private const int PlayerOther = 0x36a50;
public override int Pockets1 => PlayerOther + 0x10;
public override int Pockets2 => Pockets1 + (8 * Pockets1Count) + 0x18;
public override int Wallet => Pockets2 + (8 * Pockets2Count) + 0x18;
public override int ItemChest => PlayerOther + 0x18C;
public override int ItemCollectBit => PlayerOther + 0xA058;
public override int ItemRemakeCollectBit => PlayerOther + 0xA7AC;
public override int Manpu => PlayerOther + 0xAF7C;
public override int Bank => PlayerOther + 0x22594;
public override int Recipes => Bank + 0x10;
public override int Pockets1 => PlayerOther + 0x10;
public override int Pockets2 => Pockets1 + (8 * Pockets1Count) + 0x18;
public override int Wallet => Pockets2 + (8 * Pockets2Count) + 0x18;
public override int ItemChest => PlayerOther + 0x18C;
public override int ItemCollectBit => PlayerOther + 0xA058;
public override int ItemRemakeCollectBit => PlayerOther + 0xA7AC;
public override int Manpu => PlayerOther + 0xAF7C;
public override int Bank => PlayerOther + 0x22594;
public override int Recipes => Bank + 0x10;
public override int MaxRecipeID => 0x308;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override int MaxRecipeID => 0x308;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override IReactionStore ReadReactions(byte[] data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(byte[] data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data, Manpu);
}
}
public override IReactionStore ReadReactions(ReadOnlySpan<byte> data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(Span<byte> data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data[Manpu..]);
}

View File

@ -1,47 +1,48 @@
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;
private const int Player = 0x110;
public override int PersonalId => Player + 0xAFA8;
public override int EventFlagsPlayer => Player + 0xAFE0;
public override int PersonalId => Player + 0xAFA8;
public override int EventFlagsPlayer => Player + 0xAFE0;
private const int GSaveLifeSupport = Player + 0xBFE0;
public override int CountAchievement => GSaveLifeSupport + 0xE98; // CountAchievement
private const int GSaveLifeSupport = Player + 0xBFE0;
public override int CountAchievement => GSaveLifeSupport + 0xE98; // CountAchievement
public override int NowPoint => GSaveLifeSupport + 0x5498; // Nook Miles
public override int TotalPoint => NowPoint + 8; // Total Nook Miles Earned
public override int Birthday => Player + 0x1228c;
public override int NowPoint => GSaveLifeSupport + 0x5498; // Nook Miles
public override int TotalPoint => NowPoint + 8; // Total Nook Miles Earned
public override int Birthday => Player + 0x1228c;
public override int ProfileMain => Player + 0x122a0;
public override int ProfilePhoto => ProfileMain + 0x14;
public override int ProfileBirthday => ProfileMain + 0x23058;
public override int ProfileFruit => ProfileMain + 0x2305C;
public override int ProfileTimestamp => ProfileMain + 0x230CC;
public override int ProfileIsMakeVillage => ProfileMain + 0x230D0;
public override int ProfileMain => Player + 0x122a0;
public override int ProfilePhoto => ProfileMain + 0x14;
public override int ProfileBirthday => ProfileMain + 0x23058;
public override int ProfileFruit => ProfileMain + 0x2305C;
public override int ProfileTimestamp => ProfileMain + 0x230CC;
public override int ProfileIsMakeVillage => ProfileMain + 0x230D0;
// end player
// end player
private const int PlayerOther = 0x36a50;
private const int PlayerOther = 0x36a50;
public override int Pockets1 => PlayerOther + 0x10;
public override int Pockets2 => Pockets1 + (8 * Pockets1Count) + 0x18;
public override int Wallet => Pockets2 + (8 * Pockets2Count) + 0x18;
public override int ItemChest => PlayerOther + 0x18C;
public override int ItemCollectBit => PlayerOther + 0xA058;
public override int ItemRemakeCollectBit => PlayerOther + 0xA7AC;
public override int Manpu => PlayerOther + 0xAF7C;
public override int Bank => PlayerOther + 0x24afc;
public override int Recipes => Bank + 0x10;
public override int Pockets1 => PlayerOther + 0x10;
public override int Pockets2 => Pockets1 + (8 * Pockets1Count) + 0x18;
public override int Wallet => Pockets2 + (8 * Pockets2Count) + 0x18;
public override int ItemChest => PlayerOther + 0x18C;
public override int ItemCollectBit => PlayerOther + 0xA058;
public override int ItemRemakeCollectBit => PlayerOther + 0xA7AC;
public override int Manpu => PlayerOther + 0xAF7C;
public override int Bank => PlayerOther + 0x24afc;
public override int Recipes => Bank + 0x10;
public override int MaxRecipeID => 0x430;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override int MaxRecipeID => 0x430;
public override int MaxRemakeBitFlag => 0x7D0 * 32;
public override IReactionStore ReadReactions(byte[] data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(byte[] data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data, Manpu);
}
}
public override IReactionStore ReadReactions(ReadOnlySpan<byte> data) => data.Slice(Manpu, GSavePlayerManpu15.SIZE).ToStructure<GSavePlayerManpu15>();
public override void SetReactions(Span<byte> data, IReactionStore value) => ((GSavePlayerManpu15)value).ToBytes().CopyTo(data[Manpu..]);
}

View File

@ -1,30 +1,29 @@
using System;
namespace NHSE.Core
{
/// <summary>
/// Offset info and object retrieval logic for <see cref="Personal"/>
/// </summary>
public abstract class WhereAreNOffsets
{
public abstract int Poki { get; }
namespace NHSE.Core;
public static WhereAreNOffsets GetOffsets(FileHeaderInfo Info)
/// <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)
{
var rev = Info.GetKnownRevisionIndex();
return rev switch
{
var rev = Info.GetKnownRevisionIndex();
return rev switch
{
22 => new WhereAreNOffsets20(),
23 => new WhereAreNOffsets20(),
24 => new WhereAreNOffsets20(),
25 => new WhereAreNOffsets20(),
26 => new WhereAreNOffsets20(),
27 => new WhereAreNOffsets20(),
28 => new WhereAreNOffsets20(),
29 => new WhereAreNOffsets20(),
30 => new WhereAreNOffsets20(),
_ => throw new IndexOutOfRangeException("Unknown revision!" + Environment.NewLine + Info),
};
}
22 => new WhereAreNOffsets20(),
23 => new WhereAreNOffsets20(),
24 => new WhereAreNOffsets20(),
25 => new WhereAreNOffsets20(),
26 => new WhereAreNOffsets20(),
27 => new WhereAreNOffsets20(),
28 => new WhereAreNOffsets20(),
29 => new WhereAreNOffsets20(),
30 => new WhereAreNOffsets20(),
_ => throw new IndexOutOfRangeException("Unknown revision!" + Environment.NewLine + Info),
};
}
}

View File

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

View File

@ -1,36 +1,35 @@
namespace NHSE.Core
namespace NHSE.Core;
/// <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;
public static GameStrings Strings { get; private set; } = GetStrings(CurrentLanguage);
/// <summary>
/// Global repository for game strings; initialized to a specified language.
/// Gets the Game Strings for a specific language.
/// </summary>
public static class GameInfo
/// <param name="lang">2 character language ID</param>
public static GameStrings GetStrings(string lang)
{
private static readonly GameStrings?[] Languages = new GameStrings[GameLanguage.LanguageCount];
public static string CurrentLanguage { get; private set; } = GameLanguage.DefaultLanguage;
public static GameStrings Strings { get; private set; } = GetStrings(CurrentLanguage);
/// <summary>
/// Gets the Game Strings for a specific language.
/// </summary>
/// <param name="lang">2 character language ID</param>
public static GameStrings GetStrings(string lang)
{
int index = GameLanguage.GetLanguageIndex(lang);
return GetStrings(index);
}
public static GameStrings GetStrings(int index)
{
return Languages[index] ??= new GameStrings(GameLanguage.Language2Char(index));
}
public static string SetLanguage2Char(int index)
{
var lang = GameLanguage.Language2Char(index);
CurrentLanguage = lang;
Strings = GetStrings(lang);
return lang;
}
int index = GameLanguage.GetLanguageIndex(lang);
return GetStrings(index);
}
}
public static GameStrings GetStrings(int index)
{
return Languages[index] ??= new GameStrings(GameLanguage.Language2Char(index));
}
public static string SetLanguage2Char(int index)
{
var lang = GameLanguage.Language2Char(index);
CurrentLanguage = lang;
Strings = GetStrings(lang);
return lang;
}
}

View File

@ -1,36 +1,35 @@
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];
public static int LanguageCount => LanguageCodes.Length;
public static int GetLanguageIndex(string lang)
{
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];
int l = Array.IndexOf(LanguageCodes, lang);
return l < 0 ? DefaultLanguageIndex : l;
}
public static int LanguageCount => LanguageCodes.Length;
/// <summary>
/// Language codes supported for loading string resources
/// </summary>
private static readonly string[] LanguageCodes = ["en", "jp", "de", "es", "fr", "it", "ko", "zhs", "zht"];
public static int GetLanguageIndex(string lang)
{
int l = Array.IndexOf(LanguageCodes, lang);
return l < 0 ? DefaultLanguageIndex : l;
}
public static string[] GetStrings(string ident, string lang, string type = "text")
{
string[] data = ResourceUtil.GetStringList(ident, lang, type);
if (data.Length == 0)
data = ResourceUtil.GetStringList(ident, DefaultLanguage, type);
/// <summary>
/// Language codes supported for loading string resources
/// </summary>
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")
{
string[] data = ResourceUtil.GetStringList(ident, lang, type);
if (data.Length == 0)
data = ResourceUtil.GetStringList(ident, DefaultLanguage, type);
return data;
}
return data;
}
}

View File

@ -1,255 +1,246 @@
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;
public readonly string[] itemlist;
public readonly string[] itemlistdisplay;
public readonly string[] villagerDefaultPhrases;
public readonly Dictionary<string, string> VillagerMap;
public readonly Dictionary<string, string> VillagerDefaultPhraseMap;
public readonly List<ComboItem> ItemDataSource;
public readonly Dictionary<string, string> InternalNameTranslation = [];
public IReadOnlyDictionary<string, string> BodyParts { get; }
public IReadOnlyDictionary<string, string> BodyColor { get; }
public IReadOnlyDictionary<string, string> FabricParts { get; }
public IReadOnlyDictionary<string, string> FabricColor { get; }
private string[] Get(string ident) => GameLanguage.GetStrings(ident, lang);
public GameStrings(string l)
{
private readonly string lang;
lang = l;
villagers = Get("villager");
VillagerMap = GetMap(villagers);
villagerDefaultPhrases = Get("phrase");
VillagerDefaultPhraseMap = GetMap(villagerDefaultPhrases);
itemlist = Get("item");
itemlistdisplay = GetItemDisplayList(itemlist);
ItemDataSource = CreateItemDataSource(itemlistdisplay);
public readonly string[] villagers;
public readonly string[] itemlist;
public readonly string[] itemlistdisplay;
public readonly string[] villagerDefaultPhrases;
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 IReadOnlyDictionary<string, string> BodyParts { get; }
public IReadOnlyDictionary<string, string> BodyColor { get; }
public IReadOnlyDictionary<string, string> FabricParts { get; }
public IReadOnlyDictionary<string, string> FabricColor { get; }
private string[] Get(string ident) => GameLanguage.GetStrings(ident, lang);
public GameStrings(string l)
{
lang = l;
villagers = Get("villager");
VillagerMap = GetMap(villagers);
villagerDefaultPhrases = Get("phrase");
VillagerDefaultPhraseMap = GetMap(villagerDefaultPhrases);
itemlist = Get("item");
itemlistdisplay = GetItemDisplayList(itemlist);
ItemDataSource = CreateItemDataSource(itemlistdisplay);
BodyParts = GetDictionary(Get("body_parts"));
BodyColor = GetDictionary(Get("body_color"));
FabricParts = GetDictionary(Get("fabric_parts"));
FabricColor = GetDictionary(Get("fabric_color"));
}
private static IReadOnlyDictionary<string, string> GetDictionary(IEnumerable<string> lines, char split = '\t')
{
var result = new Dictionary<string, string>();
foreach (var s in lines)
{
if (s.Length == 0)
continue;
var index = s.IndexOf(split);
var key = s.Substring(0, index);
var value = s.Substring(index + 1);
result.Add(key, value);
}
return result;
}
private List<ComboItem> CreateItemDataSource(string[] strings)
{
var dataSource = ComboItemUtil.GetArray(strings);
// load special
dataSource.Add(new ComboItem(itemlist[0], Item.NONE));
dataSource.SortByText();
return dataSource;
}
public List<ComboItem> CreateItemDataSource(IReadOnlyCollection<ushort> dict, bool none = true)
{
var display = itemlistdisplay;
var result = new List<ComboItem>(dict.Count);
foreach (var x in dict)
result.Add(new ComboItem(display[x], x));
if (none)
result.Add(new ComboItem(itemlist[0], Item.NONE));
result.SortByText();
return result;
}
public List<ComboItem> CreateItemDataSource(IReadOnlyCollection<KeyValuePair<ushort, ushort>> dict, bool none = true)
{
var display = itemlistdisplay;
var result = new List<ComboItem>(dict.Count);
foreach (var x in dict)
result.Add(new ComboItem(display[x.Value], x.Key));
if (none)
result.Add(new ComboItem(itemlist[0], Item.NONE));
result.SortByText();
return result;
}
private static Dictionary<string, string> GetMap(IReadOnlyCollection<string> arr)
{
var map = new Dictionary<string, string>(arr.Count);
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);
map.Add(abbrev, name);
}
return map;
}
public string GetVillager(string name)
{
return VillagerMap.TryGetValue(name, out var result) ? result : 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 static string[] GetItemDisplayList(string[] items)
{
items = (string[])items.Clone();
items[0] = string.Empty;
var set = new HashSet<string>();
for (int i = 0; i < items.Length; i++)
{
var item = items[i];
if (string.IsNullOrEmpty(item))
items[i] = $"(Item #{i:000})";
else if (set.Contains(item))
items[i] += $" (#{i:000})";
else
set.Add(item);
}
return items;
}
public string GetItemName(Item item)
{
var index = item.ItemId;
if (index == Item.NONE)
return itemlist[0];
if (index == Item.EXTENSION)
return GetItemName(item.ExtensionItemId);
var kind = ItemInfo.GetItemKind(index);
if (kind.IsFlowerGene(index))
{
var display = GetItemName(index);
if (item.Genes != 0)
return $"{display} - {item.Genes}";
}
if (kind == ItemKind.Kind_DIYRecipe || kind == ItemKind.Kind_MessageBottle)
{
var display = itemlistdisplay[index];
var recipeID = (ushort)item.FreeParam;
var isKnown = RecipeList.Recipes.TryGetValue(recipeID, out var result);
var makes = isKnown ? GetItemName(result) : recipeID.ToString("000");
return $"{display} - {makes}";
}
if (kind == ItemKind.Kind_FossilUnknown)
{
var display = itemlistdisplay[index];
var fossilID = (ushort)item.FreeParam;
var fossilName = GetItemName(fossilID);
return $"{display} - {fossilName}";
}
if (kind == ItemKind.Kind_Tree)
{
var display = GetItemName(index);
var willDrop = item.Count;
if (willDrop != 0)
{
var dropName = GetItemName(willDrop);
return $"{display} - {dropName}";
}
}
return GetItemName(index);
}
public string GetItemName(ushort index)
{
if (index >= itemlistdisplay.Length)
return GetItemName60000(index);
return itemlistdisplay[index];
}
private static string GetItemName60000(ushort index)
{
if (FieldItemList.Items.TryGetValue(index, out var val))
return val.Name;
// 63,000 ???
if (index == Item.LLOYD)
return "Lloyd";
return "???";
}
/// <summary>
/// Returns clothing or item recolors not a part of ItemRemake with brackets in their names
/// </summary>
/// <param name="id">ItemID of the color variation search</param>
/// <param name="baseItemName">Item name without the associated recolors</param>
/// <returns>Map of ItemID, ItemName</returns>
public List<ComboItem> GetAssociatedItems(ushort id, out string baseItemName)
{
baseItemName = string.Empty;
var stringMatch = GetItemName(id);
var index = stringMatch.IndexOf('(');
if (index < 0)
return new List<ComboItem>();
var search = baseItemName = stringMatch.Substring(0, index);
if (!string.IsNullOrWhiteSpace(search))
return ItemDataSource.FindAll(x => x.Text.StartsWith(search));
else
return new List<ComboItem>();
}
public bool HasAssociatedItems(string baseName, out List<ComboItem>? items)
{
if (string.IsNullOrWhiteSpace(baseName))
{
items = null;
return false;
}
baseName = baseName.Trim().ToLower();
if (!baseName.EndsWith(" "))
baseName += " ";
baseName += "(";
items = ItemDataSource.FindAll(x => x.Text.ToLower().StartsWith(baseName));
return items.Count > 0;
}
BodyParts = GetDictionary(Get("body_parts"));
BodyColor = GetDictionary(Get("body_color"));
FabricParts = GetDictionary(Get("fabric_parts"));
FabricColor = GetDictionary(Get("fabric_color"));
}
public interface IRemakeString
private static Dictionary<string, string> GetDictionary(ReadOnlySpan<string> lines, char split = '\t')
{
IReadOnlyDictionary<string, string> BodyParts { get; }
IReadOnlyDictionary<string, string> BodyColor { get; }
IReadOnlyDictionary<string, string> FabricParts { get; }
IReadOnlyDictionary<string, string> FabricColor { get; }
var result = new Dictionary<string, string>();
foreach (var s in lines)
{
if (s.Length == 0)
continue;
var index = s.IndexOf(split);
var key = s[..index];
var value = s[(index + 1)..];
result.Add(key, value);
}
return result;
}
private List<ComboItem> CreateItemDataSource(ReadOnlySpan<string> strings)
{
var dataSource = ComboItemUtil.GetArray(strings);
// load special
dataSource.Add(new ComboItem(itemlist[0], Item.NONE));
dataSource.SortByText();
return dataSource;
}
public List<ComboItem> CreateItemDataSource(ReadOnlySpan<ushort> dict, bool none = true)
{
var display = itemlistdisplay;
var result = new List<ComboItem>(dict.Length);
foreach (var x in dict)
result.Add(new ComboItem(display[x], x));
if (none)
result.Add(new ComboItem(itemlist[0], Item.NONE));
result.SortByText();
return result;
}
public List<ComboItem> CreateItemDataSource(IReadOnlyCollection<KeyValuePair<ushort, ushort>> dict, bool none = true)
{
var display = itemlistdisplay;
var result = new List<ComboItem>(dict.Count);
foreach (var x in dict)
result.Add(new ComboItem(display[x.Value], x.Key));
if (none)
result.Add(new ComboItem(itemlist[0], Item.NONE));
result.SortByText();
return result;
}
private static Dictionary<string, string> GetMap(ReadOnlySpan<string> arr)
{
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[..index];
var name = kvp[(index + 1)..];
map.Add(abbrev, name);
}
return map;
}
public string GetVillager(string name) => VillagerMap.GetValueOrDefault(name, name);
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(ReadOnlySpan<string> arr)
{
var items = arr.ToArray();
items[0] = string.Empty;
var set = new HashSet<string>();
for (int i = 0; i < items.Length; i++)
{
var item = items[i];
if (string.IsNullOrEmpty(item))
items[i] = $"(Item #{i:000})";
else if (!set.Add(item))
items[i] += $" (#{i:000})";
}
return items;
}
public string GetItemName(Item item)
{
var index = item.ItemId;
if (index == Item.NONE)
return itemlist[0];
if (index == Item.EXTENSION)
return GetItemName(item.ExtensionItemId);
var kind = ItemInfo.GetItemKind(index);
if (kind.IsFlowerGene(index))
{
var display = GetItemName(index);
if (item.Genes != 0)
return $"{display} - {item.Genes}";
}
if (kind is ItemKind.Kind_DIYRecipe or ItemKind.Kind_MessageBottle)
{
var display = itemlistdisplay[index];
var recipeID = (ushort)item.FreeParam;
var isKnown = RecipeList.Recipes.TryGetValue(recipeID, out var result);
var makes = isKnown ? GetItemName(result) : recipeID.ToString("000");
return $"{display} - {makes}";
}
if (kind == ItemKind.Kind_FossilUnknown)
{
var display = itemlistdisplay[index];
var fossilID = (ushort)item.FreeParam;
var fossilName = GetItemName(fossilID);
return $"{display} - {fossilName}";
}
if (kind == ItemKind.Kind_Tree)
{
var display = GetItemName(index);
var willDrop = item.Count;
if (willDrop != 0)
{
var dropName = GetItemName(willDrop);
return $"{display} - {dropName}";
}
}
return GetItemName(index);
}
public string GetItemName(ushort index)
{
if (index >= itemlistdisplay.Length)
return GetItemName60000(index);
return itemlistdisplay[index];
}
private static string GetItemName60000(ushort index)
{
if (FieldItemList.Items.TryGetValue(index, out var val))
return val.Name;
// 63,000 ???
if (index == Item.LLOYD)
return "Lloyd";
return "???";
}
/// <summary>
/// Returns clothing or item recolors not a part of ItemRemake with brackets in their names
/// </summary>
/// <param name="id">ItemID of the color variation search</param>
/// <param name="baseItemName">Item name without the associated recolors</param>
/// <returns>Map of ItemID, ItemName</returns>
public List<ComboItem> GetAssociatedItems(ushort id, out string baseItemName)
{
baseItemName = string.Empty;
var stringMatch = GetItemName(id);
var index = stringMatch.IndexOf('(');
if (index < 0)
return [];
var search = baseItemName = stringMatch[..index];
if (!string.IsNullOrWhiteSpace(search))
return ItemDataSource.FindAll(x => x.Text.StartsWith(search));
return [];
}
public bool HasAssociatedItems(string baseName, out List<ComboItem>? items)
{
if (string.IsNullOrWhiteSpace(baseName))
{
items = null;
return false;
}
baseName = baseName.Trim().ToLower();
if (!baseName.EndsWith(' '))
baseName += " ";
baseName += "(";
items = ItemDataSource.FindAll(x => x.Text.StartsWith(baseName, StringComparison.CurrentCultureIgnoreCase));
return items.Count > 0;
}
}
public interface IRemakeString
{
IReadOnlyDictionary<string, string> BodyParts { get; }
IReadOnlyDictionary<string, string> BodyColor { get; }
IReadOnlyDictionary<string, string> FabricParts { get; }
IReadOnlyDictionary<string, string> FabricColor { get; }
}

View File

@ -1,18 +1,17 @@
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,
BridgeLog = 0x04,
BridgeRed = 0x05,
BridgeIron = 0x06,
BridgeReserved = 0x07,
BridgeWood = 0x0A,
BridgeBricks = 0x0B,
}
}
BridgeStone = 0x00,
BridgeSuspension = 0x01,
BridgeJapanese = 0x02,
BridgeLog = 0x04,
BridgeRed = 0x05,
BridgeIron = 0x06,
BridgeReserved = 0x07,
BridgeWood = 0x0A,
BridgeBricks = 0x0B,
}

View File

@ -1,63 +1,62 @@
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,
BridgeStoneDiagonal025 = 0x03,
BridgeStoneDiagonal030 = 0x04,
BridgeStoneDiagonal035 = 0x05,
BridgeSuspension04 = 0x06,
BridgeSuspension03 = 0x07,
BridgeSuspension05 = 0x08,
BridgeSuspensionDiagonal025 = 0x09,
BridgeSuspensionDiagonal030 = 0x0A,
BridgeSuspensionDiagonal035 = 0x0B,
BridgeJapanese04 = 0x0C,
BridgeLog04 = 0x0D,
BridgeLog03 = 0x0E,
BridgeLog05 = 0x0F,
BridgeLogDiagonal025 = 0x10,
BridgeLogDiagonal030 = 0x11,
BridgeLogDiagonal035 = 0x12,
BridgeJapanese03 = 0x13,
BridgeJapanese05 = 0x14,
BridgeJapaneseDiagonal025 = 0x15,
BridgeJapaneseDiagonal030 = 0x16,
BridgeJapaneseDiagonal035 = 0x17,
BridgeRed04 = 0x18,
BridgeIron04 = 0x19,
BridgeReserved03 = 0x1A,
BridgeReserved04 = 0x1B,
BridgeReserved05 = 0x1C,
BridgeReservedDiagonal025 = 0x1D,
BridgeReservedDiagonal030 = 0x1E,
BridgeReservedDiagonal035 = 0x1F,
BridgeRed03 = 0x20,
BridgeRed05 = 0x21,
BridgeRedDiagonal025 = 0x22,
BridgeRedDiagonal030 = 0x23,
BridgeRedDiagonal035 = 0x24,
BridgeWood04 = 0x25,
BridgeBricks04 = 0x26,
BridgeIron03 = 0x27,
BridgeIron05 = 0x28,
BridgeIronDiagonal025 = 0x29,
BridgeIronDiagonal030 = 0x2A,
BridgeIronDiagonal035 = 0x2B,
BridgeWood03 = 0x2C,
BridgeWood05 = 0x2D,
BridgeWoodDiagonal025 = 0x2E,
BridgeWoodDiagonal030 = 0x2F,
BridgeWoodDiagonal035 = 0x30,
BridgeBricks03 = 0x31,
BridgeBricks05 = 0x34,
BridgeBricksDiagonal025 = 0x35,
BridgeBricksDiagonal030 = 0x36,
BridgeBricksDiagonal035 = 0x37,
}
}
BridgeStone03 = 0x00,
BridgeStone04 = 0x01,
BridgeStone05 = 0x02,
BridgeStoneDiagonal025 = 0x03,
BridgeStoneDiagonal030 = 0x04,
BridgeStoneDiagonal035 = 0x05,
BridgeSuspension04 = 0x06,
BridgeSuspension03 = 0x07,
BridgeSuspension05 = 0x08,
BridgeSuspensionDiagonal025 = 0x09,
BridgeSuspensionDiagonal030 = 0x0A,
BridgeSuspensionDiagonal035 = 0x0B,
BridgeJapanese04 = 0x0C,
BridgeLog04 = 0x0D,
BridgeLog03 = 0x0E,
BridgeLog05 = 0x0F,
BridgeLogDiagonal025 = 0x10,
BridgeLogDiagonal030 = 0x11,
BridgeLogDiagonal035 = 0x12,
BridgeJapanese03 = 0x13,
BridgeJapanese05 = 0x14,
BridgeJapaneseDiagonal025 = 0x15,
BridgeJapaneseDiagonal030 = 0x16,
BridgeJapaneseDiagonal035 = 0x17,
BridgeRed04 = 0x18,
BridgeIron04 = 0x19,
BridgeReserved03 = 0x1A,
BridgeReserved04 = 0x1B,
BridgeReserved05 = 0x1C,
BridgeReservedDiagonal025 = 0x1D,
BridgeReservedDiagonal030 = 0x1E,
BridgeReservedDiagonal035 = 0x1F,
BridgeRed03 = 0x20,
BridgeRed05 = 0x21,
BridgeRedDiagonal025 = 0x22,
BridgeRedDiagonal030 = 0x23,
BridgeRedDiagonal035 = 0x24,
BridgeWood04 = 0x25,
BridgeBricks04 = 0x26,
BridgeIron03 = 0x27,
BridgeIron05 = 0x28,
BridgeIronDiagonal025 = 0x29,
BridgeIronDiagonal030 = 0x2A,
BridgeIronDiagonal035 = 0x2B,
BridgeWood03 = 0x2C,
BridgeWood05 = 0x2D,
BridgeWoodDiagonal025 = 0x2E,
BridgeWoodDiagonal030 = 0x2F,
BridgeWoodDiagonal035 = 0x30,
BridgeBricks03 = 0x31,
BridgeBricks05 = 0x34,
BridgeBricksDiagonal025 = 0x35,
BridgeBricksDiagonal030 = 0x36,
BridgeBricksDiagonal035 = 0x37,
}

View File

@ -1,54 +1,54 @@
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; }
[field: FieldOffset(0x02)] public ushort X { get; set; }
[field: FieldOffset(0x04)] public ushort Y { get; set; }
[field: FieldOffset(0x06)] public byte Angle { get; set; }
[field: FieldOffset(0x07)] public sbyte Bit { get; set; }
[field: FieldOffset(0x08)] public ushort Type { get; set; }
[field: FieldOffset(0x0A)] public byte TypeArg { get; set; }
[field: FieldOffset(0x0C)] public ushort UniqueID { get; set; }
[field: FieldOffset(0x10)] public uint Unused { get; set; }
public void Clear()
{
public const int SIZE = 0x14;
[field: FieldOffset(0x00)] public BuildingType BuildingType { get; set; }
[field: FieldOffset(0x02)] public ushort X { get; set; }
[field: FieldOffset(0x04)] public ushort Y { get; set; }
[field: FieldOffset(0x06)] public byte Angle { get; set; }
[field: FieldOffset(0x07)] public sbyte Bit { get; set; }
[field: FieldOffset(0x08)] public ushort Type { get; set; }
[field: FieldOffset(0x0A)] public byte TypeArg { get; set; }
[field: FieldOffset(0x0C)] public ushort UniqueID { get; set; }
[field: FieldOffset(0x10)] public uint Unused { get; set; }
public void Clear()
{
BuildingType = 0;
X = Y = Angle = 0;
Bit = 0;
Type = TypeArg = 0;
UniqueID = 0;
Unused = 0;
}
public void CopyFrom(Building building)
{
BuildingType = building.BuildingType;
X = building.X;
Y = building.Y;
Angle = building.Angle;
Bit = building.Bit;
Type = building.Type;
TypeArg = building.TypeArg;
UniqueID = building.UniqueID;
Unused = building.Unused;
}
public static Building[] GetArray(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}";
BuildingType = 0;
X = Y = Angle = 0;
Bit = 0;
Type = TypeArg = 0;
UniqueID = 0;
Unused = 0;
}
}
public void CopyFrom(Building building)
{
BuildingType = building.BuildingType;
X = building.X;
Y = building.Y;
Angle = building.Angle;
Bit = building.Bit;
Type = building.Type;
TypeArg = building.TypeArg;
UniqueID = building.UniqueID;
Unused = building.Unused;
}
public static Building[] GetArray(ReadOnlySpan<byte> data) => data.GetArray<Building>(SIZE);
public static byte[] SetArray(IReadOnlyList<Building> data) => data.SetArray(SIZE);
public override string ToString() => $"{X:000},{Y:000} - {BuildingType}";
}

View File

@ -1,39 +1,38 @@
namespace NHSE.Core
namespace NHSE.Core;
/// <summary>
/// Building model &amp; interior that is loaded when the <see cref="Building"/> is entered.
/// </summary>
public enum BuildingType : ushort
{
/// <summary>
/// Building model &amp; interior that is loaded when the <see cref="Building"/> is entered.
/// </summary>
public enum BuildingType : ushort
{
None = 0,
PlayerHouse1 = 1,
PlayerHouse2 = 2,
PlayerHouse3 = 3,
PlayerHouse4 = 4,
PlayerHouse5 = 5,
PlayerHouse6 = 6,
PlayerHouse7 = 7,
PlayerHouse8 = 8,
Villager1 = 9,
Villager2 = 10,
Villager3 = 11,
Villager4 = 12,
Villager5 = 13,
Villager6 = 14,
Villager7 = 15,
Villager8 = 16,
Villager9 = 17,
Villager10 = 18,
NooksCranny = 19,
ResidentCenterStructure = 20,
Museum = 21,
Airport = 22,
ResidentCenterTent = 23,
AblesSisters = 24,
Campsite = 25,
Bridge = 26,
Incline = 27,
ReddsTreasureTrawler = 28,
Studio = 29,
}
}
None = 0,
PlayerHouse1 = 1,
PlayerHouse2 = 2,
PlayerHouse3 = 3,
PlayerHouse4 = 4,
PlayerHouse5 = 5,
PlayerHouse6 = 6,
PlayerHouse7 = 7,
PlayerHouse8 = 8,
Villager1 = 9,
Villager2 = 10,
Villager3 = 11,
Villager4 = 12,
Villager5 = 13,
Villager6 = 14,
Villager7 = 15,
Villager8 = 16,
Villager9 = 17,
Villager10 = 18,
NooksCranny = 19,
ResidentCenterStructure = 20,
Museum = 21,
Airport = 22,
ResidentCenterTent = 23,
AblesSisters = 24,
Campsite = 25,
Bridge = 26,
Incline = 27,
ReddsTreasureTrawler = 28,
Studio = 29,
}

View File

@ -1,262 +1,261 @@
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,
HouseDoorIronAR = 0x04,
HouseDoorIronpartsAS = 0x05,
HouseDoorIronpartsAR = 0x06,
HouseTentPADoor = 0x07,
HouseTentNADoor = 0x08,
HouseDoorReliefAS = 0x09,
HouseDoorReliefAR = 0x0A,
HouseDoorJapaneseAS = 0x0E,
HouseDoorJapaneseAR = 0x0F,
HouseDoorWindowAS = 0x10,
HouseDoorWindowAR = 0x11,
HouseDoorIronGrillAS = 0x12,
HouseDoorIronGrillAR = 0x13,
HouseDoorChineseAS = 0x14,
HouseDoorChineseAR = 0x15,
HouseDoorSimpleAS = 0x16,
HouseDoorSimpleAR = 0x17,
_1 = 0x18,
_2 = 0x19,
HouseDoorCarvingAS = 0x1A,
HouseDoorCarvingAR = 0x1B,
HouseDoorCercleWindowAS = 0x1C,
HouseDoorCercleWindowAR = 0x1D,
HouseDoorVerticalWindowAS = 0x1E,
HouseDoorVerticalWindowAR = 0x1F,
HouseDoorLatticeAS = 0x20,
HouseDoorLatticeAR = 0x21,
HouseDoorSimplicityAS = 0x22,
HouseDoorSimplicityAR = 0x23,
_3 = 0x25,
_4 = 0x26,
HouseTentNBDoor = 0x27,
HouseDoorIronGrillBS = 0x28,
HouseDoorIronGrillCS = 0x29,
HouseDoorIronGrillDS = 0x2A,
HouseDoorIronGrillES = 0x2B,
HouseDoorIronGrillFS = 0x2C,
HouseDoorIronGrillBR = 0x2D,
HouseDoorIronGrillCR = 0x2E,
HouseDoorIronGrillDR = 0x2F,
HouseDoorIronGrillER = 0x30,
HouseDoorIronGrillFR = 0x31,
HouseDoorCarvingBS = 0x32,
HouseDoorCarvingCS = 0x33,
HouseDoorCarvingDS = 0x34,
HouseDoorCarvingES = 0x35,
HouseDoorCarvingBR = 0x36,
HouseDoorCarvingCR = 0x37,
HouseDoorCarvingDR = 0x38,
HouseDoorCarvingER = 0x39,
HouseDoorIronGrillGS = 0x3A,
HouseDoorIronGrillGR = 0x3B,
HouseDoorIronGrillHS = 0x3C,
HouseDoorIronGrillHR = 0x3D,
HouseDoorVerticalWindowBS = 0x3E,
HouseDoorVerticalWindowCS = 0x3F,
HouseDoorVerticalWindowDS = 0x40,
HouseDoorVerticalWindowES = 0x41,
HouseDoorVerticalWindowFS = 0x42,
HouseDoorVerticalWindowGS = 0x43,
HouseDoorVerticalWindowHS = 0x44,
HouseDoorVerticalWindowBR = 0x45,
HouseDoorVerticalWindowCR = 0x46,
HouseDoorVerticalWindowDR = 0x47,
HouseDoorVerticalWindowER = 0x48,
HouseDoorVerticalWindowFR = 0x49,
HouseDoorVerticalWindowGR = 0x4A,
HouseDoorVerticalWindowHR = 0x4B,
HouseDoorReliefBS = 0x4C,
HouseDoorReliefCS = 0x4D,
HouseDoorReliefDS = 0x4E,
HouseDoorReliefES = 0x4F,
HouseDoorReliefFS = 0x50,
HouseDoorReliefGS = 0x51,
HouseDoorReliefHS = 0x52,
HouseDoorReliefBR = 0x53,
HouseDoorReliefCR = 0x54,
HouseDoorReliefDR = 0x55,
HouseDoorReliefER = 0x56,
HouseDoorReliefFR = 0x57,
HouseDoorReliefGR = 0x58,
HouseDoorReliefHR = 0x59,
HouseDoorIronpartsBS = 0x5A,
HouseDoorIronpartsCS = 0x5B,
HouseDoorIronpartsDS = 0x5C,
HouseDoorIronpartsES = 0x5D,
HouseDoorIronpartsFS = 0x5E,
HouseDoorIronpartsGS = 0x5F,
HouseDoorIronpartsHS = 0x60,
HouseDoorIronpartsBR = 0x61,
HouseDoorIronpartsCR = 0x62,
HouseDoorIronpartsDR = 0x63,
HouseDoorIronpartsER = 0x64,
HouseDoorIronpartsFR = 0x65,
HouseDoorIronpartsGR = 0x66,
HouseDoorIronpartsHR = 0x67,
HouseDoorSimpleBS = 0x84,
HouseDoorSimpleCS = 0x85,
HouseDoorSimpleDS = 0x86,
HouseDoorSimpleES = 0x87,
HouseDoorSimpleFS = 0x88,
HouseDoorSimpleGS = 0x89,
HouseDoorSimpleHS = 0x8A,
HouseDoorSimpleBR = 0x8B,
HouseDoorSimpleCR = 0x8C,
HouseDoorSimpleDR = 0x8D,
HouseDoorSimpleER = 0x8E,
HouseDoorSimpleFR = 0x8F,
HouseDoorSimpleGR = 0x90,
HouseDoorSimpleHR = 0x91,
HouseDoorJapaneseBS = 0x92,
HouseDoorJapaneseCS = 0x93,
HouseDoorJapaneseDS = 0x94,
HouseDoorJapaneseES = 0x95,
HouseDoorJapaneseFS = 0x96,
HouseDoorJapaneseBR = 0x97,
HouseDoorJapaneseCR = 0x98,
HouseDoorJapaneseDR = 0x99,
HouseDoorJapaneseER = 0x9A,
HouseDoorJapaneseFR = 0x9B,
HouseDoorLatticeBS = 0x9C,
HouseDoorLatticeCS = 0x9D,
HouseDoorLatticeDS = 0x9E,
HouseDoorLatticeES = 0x9F,
HouseDoorLatticeFS = 0xA0,
HouseDoorLatticeGS = 0xA1,
HouseDoorLatticeHS = 0xA2,
HouseDoorLatticeBR = 0xA3,
HouseDoorLatticeCR = 0xA4,
HouseDoorLatticeDR = 0xA5,
HouseDoorLatticeER = 0xA6,
HouseDoorLatticeFR = 0xA7,
HouseDoorLatticeGR = 0xA8,
HouseDoorLatticeHR = 0xA9,
HouseDoorCercleWindowBS = 0xAA,
HouseDoorCercleWindowCS = 0xAB,
HouseDoorCercleWindowDS = 0xAC,
HouseDoorCercleWindowES = 0xAD,
HouseDoorCercleWindowFS = 0xAE,
HouseDoorCercleWindowGS = 0xAF,
HouseDoorCercleWindowHS = 0xB0,
HouseDoorCercleWindowBR = 0xB1,
HouseDoorCercleWindowCR = 0xB2,
HouseDoorCercleWindowDR = 0xB3,
HouseDoorCercleWindowER = 0xB4,
HouseDoorCercleWindowFR = 0xB5,
HouseDoorCercleWindowGR = 0xB6,
HouseDoorCercleWindowHR = 0xB7,
HouseDoorJapaneseGS = 0xB8,
HouseDoorJapaneseGR = 0xB9,
HouseDoorSimplicityBS = 0xBA,
HouseDoorSimplicityCS = 0xBB,
HouseDoorSimplicityDS = 0xBC,
HouseDoorSimplicityES = 0xBD,
HouseDoorSimplicityFS = 0xBE,
HouseDoorSimplicityGS = 0xBF,
HouseDoorSimplicityHS = 0xC0,
HouseDoorSimplicityBR = 0xC1,
HouseDoorSimplicityCR = 0xC2,
HouseDoorSimplicityDR = 0xC3,
HouseDoorSimplicityER = 0xC4,
HouseDoorSimplicityFR = 0xC5,
HouseDoorSimplicityGR = 0xC6,
HouseDoorSimplicityHR = 0xC7,
HouseDoorChineseBS = 0xC8,
HouseDoorChineseCS = 0xC9,
HouseDoorChineseDS = 0xCA,
HouseDoorChineseES = 0xCB,
HouseDoorChineseFS = 0xCC,
HouseDoorChineseGS = 0xCD,
HouseDoorChineseHS = 0xCE,
HouseDoorChineseBR = 0xCF,
HouseDoorChineseCR = 0xD0,
HouseDoorChineseDR = 0xD1,
HouseDoorChineseER = 0xD2,
HouseDoorChineseFR = 0xD3,
HouseDoorChineseGR = 0xD4,
HouseDoorChineseHR = 0xD5,
HouseDoorWindowBS = 0xDD,
HouseDoorWindowCS = 0xDE,
HouseDoorWindowDS = 0xDF,
HouseDoorWindowES = 0xE0,
HouseDoorWindowFS = 0xE1,
HouseDoorWindowGS = 0xE2,
HouseDoorWindowHS = 0xE3,
HouseDoorWindowBR = 0xE4,
HouseDoorWindowCR = 0xE5,
HouseDoorWindowDR = 0xE6,
HouseDoorWindowER = 0xE7,
HouseDoorWindowFR = 0xE8,
HouseDoorWindowGR = 0xE9,
HouseDoorWindowHR = 0xEA,
HouseDoorIronBS = 0xEB,
HouseDoorIronCS = 0xEC,
HouseDoorIronDS = 0xED,
HouseDoorIronES = 0xEE,
HouseDoorIronFS = 0xEF,
HouseDoorIronGS = 0xF0,
HouseDoorIronHS = 0xF1,
HouseDoorIronBR = 0xF2,
HouseDoorIronCR = 0xF3,
HouseDoorIronDR = 0xF4,
HouseDoorIronER = 0xF5,
HouseDoorIronFR = 0xF6,
HouseDoorIronGR = 0xF7,
HouseDoorIronHR = 0xF8,
HouseDoorStandardBS = 0xF9,
HouseDoorStandardCS = 0xFA,
HouseDoorStandardDS = 0xFB,
HouseDoorStandardES = 0xFC,
HouseDoorStandardFS = 0xFD,
HouseDoorStandardGS = 0xFE,
HouseDoorStandardHS = 0xFF,
HouseDoorStandardBR = 0x100,
HouseDoorStandardCR = 0x101,
HouseDoorStandardDR = 0x102,
HouseDoorStandardER = 0x103,
HouseDoorStandardFR = 0x104,
HouseDoorStandardGR = 0x105,
HouseDoorStandardHR = 0x106,
HouseDoorReliefIS = 0x107,
HouseDoorReliefIR = 0x108,
HouseDoorReliefJS = 0x109,
HouseDoorReliefJR = 0x10A,
HouseDoorCarvingFS = 0x10B,
HouseDoorCarvingFR = 0x10C,
HouseDoorCercleWindowIS = 0x10D,
HouseDoorCercleWindowIR = 0x10E,
HouseDoorCercleWindowJS = 0x10F,
HouseDoorCercleWindowJR = 0x110,
HouseDoorVerticalWindowIS = 0x111,
HouseDoorVerticalWindowIR = 0x112,
HouseDoorVerticalWindowJS = 0x113,
HouseDoorVerticalWindowJR = 0x114,
HouseDoorWindowIS = 0x115,
HouseDoorWindowIR = 0x116,
HouseDoorWindowJS = 0x117,
HouseDoorWindowJR = 0x118,
HouseDoorStandardIS = 0x119,
HouseDoorStandardIR = 0x11A,
HouseDoorStandardJS = 0x11B,
HouseDoorStandardJR = 0x11C,
HouseDoorSimplicityIS = 0x11D,
HouseDoorSimplicityIR = 0x11E,
HouseDoorSimplicityJS = 0x11F,
HouseDoorSimplicityJR = 0x120,
HouseDoorIronGrillIS = 0x121,
HouseDoorIronGrillIR = 0x122,
HouseDoorIronGrillJS = 0x123,
HouseDoorIronGrillJR = 0x124,
}
}
HouseDoorStandardAR = 0x00,
HouseDoorStandardAS = 0x02,
HouseDoorIronAS = 0x03,
HouseDoorIronAR = 0x04,
HouseDoorIronpartsAS = 0x05,
HouseDoorIronpartsAR = 0x06,
HouseTentPADoor = 0x07,
HouseTentNADoor = 0x08,
HouseDoorReliefAS = 0x09,
HouseDoorReliefAR = 0x0A,
HouseDoorJapaneseAS = 0x0E,
HouseDoorJapaneseAR = 0x0F,
HouseDoorWindowAS = 0x10,
HouseDoorWindowAR = 0x11,
HouseDoorIronGrillAS = 0x12,
HouseDoorIronGrillAR = 0x13,
HouseDoorChineseAS = 0x14,
HouseDoorChineseAR = 0x15,
HouseDoorSimpleAS = 0x16,
HouseDoorSimpleAR = 0x17,
_1 = 0x18,
_2 = 0x19,
HouseDoorCarvingAS = 0x1A,
HouseDoorCarvingAR = 0x1B,
HouseDoorCercleWindowAS = 0x1C,
HouseDoorCercleWindowAR = 0x1D,
HouseDoorVerticalWindowAS = 0x1E,
HouseDoorVerticalWindowAR = 0x1F,
HouseDoorLatticeAS = 0x20,
HouseDoorLatticeAR = 0x21,
HouseDoorSimplicityAS = 0x22,
HouseDoorSimplicityAR = 0x23,
_3 = 0x25,
_4 = 0x26,
HouseTentNBDoor = 0x27,
HouseDoorIronGrillBS = 0x28,
HouseDoorIronGrillCS = 0x29,
HouseDoorIronGrillDS = 0x2A,
HouseDoorIronGrillES = 0x2B,
HouseDoorIronGrillFS = 0x2C,
HouseDoorIronGrillBR = 0x2D,
HouseDoorIronGrillCR = 0x2E,
HouseDoorIronGrillDR = 0x2F,
HouseDoorIronGrillER = 0x30,
HouseDoorIronGrillFR = 0x31,
HouseDoorCarvingBS = 0x32,
HouseDoorCarvingCS = 0x33,
HouseDoorCarvingDS = 0x34,
HouseDoorCarvingES = 0x35,
HouseDoorCarvingBR = 0x36,
HouseDoorCarvingCR = 0x37,
HouseDoorCarvingDR = 0x38,
HouseDoorCarvingER = 0x39,
HouseDoorIronGrillGS = 0x3A,
HouseDoorIronGrillGR = 0x3B,
HouseDoorIronGrillHS = 0x3C,
HouseDoorIronGrillHR = 0x3D,
HouseDoorVerticalWindowBS = 0x3E,
HouseDoorVerticalWindowCS = 0x3F,
HouseDoorVerticalWindowDS = 0x40,
HouseDoorVerticalWindowES = 0x41,
HouseDoorVerticalWindowFS = 0x42,
HouseDoorVerticalWindowGS = 0x43,
HouseDoorVerticalWindowHS = 0x44,
HouseDoorVerticalWindowBR = 0x45,
HouseDoorVerticalWindowCR = 0x46,
HouseDoorVerticalWindowDR = 0x47,
HouseDoorVerticalWindowER = 0x48,
HouseDoorVerticalWindowFR = 0x49,
HouseDoorVerticalWindowGR = 0x4A,
HouseDoorVerticalWindowHR = 0x4B,
HouseDoorReliefBS = 0x4C,
HouseDoorReliefCS = 0x4D,
HouseDoorReliefDS = 0x4E,
HouseDoorReliefES = 0x4F,
HouseDoorReliefFS = 0x50,
HouseDoorReliefGS = 0x51,
HouseDoorReliefHS = 0x52,
HouseDoorReliefBR = 0x53,
HouseDoorReliefCR = 0x54,
HouseDoorReliefDR = 0x55,
HouseDoorReliefER = 0x56,
HouseDoorReliefFR = 0x57,
HouseDoorReliefGR = 0x58,
HouseDoorReliefHR = 0x59,
HouseDoorIronpartsBS = 0x5A,
HouseDoorIronpartsCS = 0x5B,
HouseDoorIronpartsDS = 0x5C,
HouseDoorIronpartsES = 0x5D,
HouseDoorIronpartsFS = 0x5E,
HouseDoorIronpartsGS = 0x5F,
HouseDoorIronpartsHS = 0x60,
HouseDoorIronpartsBR = 0x61,
HouseDoorIronpartsCR = 0x62,
HouseDoorIronpartsDR = 0x63,
HouseDoorIronpartsER = 0x64,
HouseDoorIronpartsFR = 0x65,
HouseDoorIronpartsGR = 0x66,
HouseDoorIronpartsHR = 0x67,
HouseDoorSimpleBS = 0x84,
HouseDoorSimpleCS = 0x85,
HouseDoorSimpleDS = 0x86,
HouseDoorSimpleES = 0x87,
HouseDoorSimpleFS = 0x88,
HouseDoorSimpleGS = 0x89,
HouseDoorSimpleHS = 0x8A,
HouseDoorSimpleBR = 0x8B,
HouseDoorSimpleCR = 0x8C,
HouseDoorSimpleDR = 0x8D,
HouseDoorSimpleER = 0x8E,
HouseDoorSimpleFR = 0x8F,
HouseDoorSimpleGR = 0x90,
HouseDoorSimpleHR = 0x91,
HouseDoorJapaneseBS = 0x92,
HouseDoorJapaneseCS = 0x93,
HouseDoorJapaneseDS = 0x94,
HouseDoorJapaneseES = 0x95,
HouseDoorJapaneseFS = 0x96,
HouseDoorJapaneseBR = 0x97,
HouseDoorJapaneseCR = 0x98,
HouseDoorJapaneseDR = 0x99,
HouseDoorJapaneseER = 0x9A,
HouseDoorJapaneseFR = 0x9B,
HouseDoorLatticeBS = 0x9C,
HouseDoorLatticeCS = 0x9D,
HouseDoorLatticeDS = 0x9E,
HouseDoorLatticeES = 0x9F,
HouseDoorLatticeFS = 0xA0,
HouseDoorLatticeGS = 0xA1,
HouseDoorLatticeHS = 0xA2,
HouseDoorLatticeBR = 0xA3,
HouseDoorLatticeCR = 0xA4,
HouseDoorLatticeDR = 0xA5,
HouseDoorLatticeER = 0xA6,
HouseDoorLatticeFR = 0xA7,
HouseDoorLatticeGR = 0xA8,
HouseDoorLatticeHR = 0xA9,
HouseDoorCercleWindowBS = 0xAA,
HouseDoorCercleWindowCS = 0xAB,
HouseDoorCercleWindowDS = 0xAC,
HouseDoorCercleWindowES = 0xAD,
HouseDoorCercleWindowFS = 0xAE,
HouseDoorCercleWindowGS = 0xAF,
HouseDoorCercleWindowHS = 0xB0,
HouseDoorCercleWindowBR = 0xB1,
HouseDoorCercleWindowCR = 0xB2,
HouseDoorCercleWindowDR = 0xB3,
HouseDoorCercleWindowER = 0xB4,
HouseDoorCercleWindowFR = 0xB5,
HouseDoorCercleWindowGR = 0xB6,
HouseDoorCercleWindowHR = 0xB7,
HouseDoorJapaneseGS = 0xB8,
HouseDoorJapaneseGR = 0xB9,
HouseDoorSimplicityBS = 0xBA,
HouseDoorSimplicityCS = 0xBB,
HouseDoorSimplicityDS = 0xBC,
HouseDoorSimplicityES = 0xBD,
HouseDoorSimplicityFS = 0xBE,
HouseDoorSimplicityGS = 0xBF,
HouseDoorSimplicityHS = 0xC0,
HouseDoorSimplicityBR = 0xC1,
HouseDoorSimplicityCR = 0xC2,
HouseDoorSimplicityDR = 0xC3,
HouseDoorSimplicityER = 0xC4,
HouseDoorSimplicityFR = 0xC5,
HouseDoorSimplicityGR = 0xC6,
HouseDoorSimplicityHR = 0xC7,
HouseDoorChineseBS = 0xC8,
HouseDoorChineseCS = 0xC9,
HouseDoorChineseDS = 0xCA,
HouseDoorChineseES = 0xCB,
HouseDoorChineseFS = 0xCC,
HouseDoorChineseGS = 0xCD,
HouseDoorChineseHS = 0xCE,
HouseDoorChineseBR = 0xCF,
HouseDoorChineseCR = 0xD0,
HouseDoorChineseDR = 0xD1,
HouseDoorChineseER = 0xD2,
HouseDoorChineseFR = 0xD3,
HouseDoorChineseGR = 0xD4,
HouseDoorChineseHR = 0xD5,
HouseDoorWindowBS = 0xDD,
HouseDoorWindowCS = 0xDE,
HouseDoorWindowDS = 0xDF,
HouseDoorWindowES = 0xE0,
HouseDoorWindowFS = 0xE1,
HouseDoorWindowGS = 0xE2,
HouseDoorWindowHS = 0xE3,
HouseDoorWindowBR = 0xE4,
HouseDoorWindowCR = 0xE5,
HouseDoorWindowDR = 0xE6,
HouseDoorWindowER = 0xE7,
HouseDoorWindowFR = 0xE8,
HouseDoorWindowGR = 0xE9,
HouseDoorWindowHR = 0xEA,
HouseDoorIronBS = 0xEB,
HouseDoorIronCS = 0xEC,
HouseDoorIronDS = 0xED,
HouseDoorIronES = 0xEE,
HouseDoorIronFS = 0xEF,
HouseDoorIronGS = 0xF0,
HouseDoorIronHS = 0xF1,
HouseDoorIronBR = 0xF2,
HouseDoorIronCR = 0xF3,
HouseDoorIronDR = 0xF4,
HouseDoorIronER = 0xF5,
HouseDoorIronFR = 0xF6,
HouseDoorIronGR = 0xF7,
HouseDoorIronHR = 0xF8,
HouseDoorStandardBS = 0xF9,
HouseDoorStandardCS = 0xFA,
HouseDoorStandardDS = 0xFB,
HouseDoorStandardES = 0xFC,
HouseDoorStandardFS = 0xFD,
HouseDoorStandardGS = 0xFE,
HouseDoorStandardHS = 0xFF,
HouseDoorStandardBR = 0x100,
HouseDoorStandardCR = 0x101,
HouseDoorStandardDR = 0x102,
HouseDoorStandardER = 0x103,
HouseDoorStandardFR = 0x104,
HouseDoorStandardGR = 0x105,
HouseDoorStandardHR = 0x106,
HouseDoorReliefIS = 0x107,
HouseDoorReliefIR = 0x108,
HouseDoorReliefJS = 0x109,
HouseDoorReliefJR = 0x10A,
HouseDoorCarvingFS = 0x10B,
HouseDoorCarvingFR = 0x10C,
HouseDoorCercleWindowIS = 0x10D,
HouseDoorCercleWindowIR = 0x10E,
HouseDoorCercleWindowJS = 0x10F,
HouseDoorCercleWindowJR = 0x110,
HouseDoorVerticalWindowIS = 0x111,
HouseDoorVerticalWindowIR = 0x112,
HouseDoorVerticalWindowJS = 0x113,
HouseDoorVerticalWindowJR = 0x114,
HouseDoorWindowIS = 0x115,
HouseDoorWindowIR = 0x116,
HouseDoorWindowJS = 0x117,
HouseDoorWindowJR = 0x118,
HouseDoorStandardIS = 0x119,
HouseDoorStandardIR = 0x11A,
HouseDoorStandardJS = 0x11B,
HouseDoorStandardJR = 0x11C,
HouseDoorSimplicityIS = 0x11D,
HouseDoorSimplicityIR = 0x11E,
HouseDoorSimplicityJS = 0x11F,
HouseDoorSimplicityJR = 0x120,
HouseDoorIronGrillIS = 0x121,
HouseDoorIronGrillIR = 0x122,
HouseDoorIronGrillJS = 0x123,
HouseDoorIronGrillJR = 0x124,
}

View File

@ -1,175 +1,174 @@
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,
_2 = 0x05,
HouseRoofPA04ThatchedA = 0x06,
HouseRoofPA04StoneA = 0x07,
HouseRoofPA04WesterntileA = 0x08,
_3 = 0x09,
HouseRoofPA00StandardA = 0x0A,
HouseRoofPA00StandardB = 0x0B,
HouseRoofPA00StandardC = 0x0C,
HouseRoofPA00StandardD = 0x0D,
HouseRoofPA00StandardE = 0x0E,
HouseRoofPA00StandardF = 0x0F,
HouseRoofPA00StandardG = 0x10,
HouseRoofPA00StandardH = 0x11,
_4 = 0x12,
HouseRoofNASlateA = 0x13,
HouseRoofNAMetalA = 0x14,
HouseRoofNATileA = 0x15,
HouseRoofPA04ThatchedB = 0x17,
HouseRoofPA04ThatchedC = 0x18,
HouseRoofPA04ThatchedD = 0x19,
HouseRoofPA04ThatchedE = 0x1A,
HouseRoofNBWoodtileA = 0x1B,
HouseRoofNBThatchedA = 0x1C,
HouseRoofNBWoodpanelA = 0x1D,
HouseRoofNCJapaneseA = 0x1E,
HouseRoofNCThatchedA = 0x1F,
HouseRoofNCStoneweightA = 0x20,
_5 = 0x21,
_6 = 0x22,
HouseRoofNDStoneA = 0x23,
HouseRoofNDThatchedA = 0x24,
HouseRoofNDWoodA = 0x25,
HouseRoofPA01StandardA = 0x26,
HouseRoofPA03StandardA = 0x27,
HouseRoofNATileB = 0x28,
HouseRoofNATileC = 0x29,
HouseRoofNATileD = 0x2A,
HouseRoofNATileE = 0x2B,
HouseRoofNATileF = 0x2C,
HouseRoofNATileG = 0x2D,
HouseRoofNATileH = 0x2E,
HouseRoofPA02StandardA = 0x2F,
HouseRoofNAMetalB = 0x30,
HouseRoofNAMetalC = 0x31,
HouseRoofNAMetalD = 0x32,
HouseRoofNAMetalE = 0x33,
HouseRoofNAMetalF = 0x34,
HouseRoofNAMetalG = 0x35,
HouseRoofNAMetalH = 0x36,
HouseRoofNASlateB = 0x37,
HouseRoofNASlateC = 0x38,
HouseRoofNASlateD = 0x39,
HouseRoofNASlateE = 0x3A,
HouseRoofNASlateF = 0x3B,
HouseRoofNASlateG = 0x3C,
HouseRoofNASlateH = 0x3D,
HouseRoofNBWoodtileB = 0x3E,
HouseRoofNBWoodtileC = 0x3F,
HouseRoofNBWoodtileD = 0x40,
HouseRoofNBWoodtileE = 0x41,
HouseRoofNBWoodtileF = 0x42,
HouseRoofNBWoodtileG = 0x43,
HouseRoofNBWoodtileH = 0x44,
HouseRoofNBWoodpanelB = 0x45,
HouseRoofNBWoodpanelC = 0x46,
HouseRoofNBWoodpanelD = 0x47,
HouseRoofNBWoodpanelE = 0x48,
HouseRoofNBWoodpanelF = 0x49,
HouseRoofNBWoodpanelG = 0x4A,
HouseRoofNBWoodpanelH = 0x4B,
HouseRoofPA01StandardB = 0x4C,
HouseRoofPA01StandardC = 0x4D,
HouseRoofPA01StandardD = 0x4E,
HouseRoofPA01StandardE = 0x4F,
HouseRoofPA01StandardF = 0x50,
HouseRoofPA01StandardG = 0x51,
HouseRoofPA01StandardH = 0x52,
HouseRoofNBThatchedB = 0x53,
HouseRoofNBThatchedC = 0x54,
HouseRoofNBThatchedD = 0x55,
HouseRoofNBThatchedE = 0x56,
HouseRoofNBThatchedF = 0x57,
HouseRoofNBThatchedG = 0x58,
HouseRoofNBThatchedH = 0x59,
HouseRoofNCJapaneseB = 0x5A,
HouseRoofNCJapaneseC = 0x5B,
HouseRoofNCJapaneseD = 0x5C,
HouseRoofNCJapaneseE = 0x5D,
HouseRoofNCJapaneseF = 0x5E,
HouseRoofNCJapaneseG = 0x5F,
HouseRoofNCJapaneseH = 0x60,
HouseRoofNCStoneweightB = 0x61,
HouseRoofNCStoneweightC = 0x62,
HouseRoofNCStoneweightD = 0x63,
HouseRoofNCStoneweightE = 0x64,
HouseRoofNCStoneweightF = 0x65,
HouseRoofNDWoodB = 0x66,
HouseRoofNDWoodC = 0x67,
HouseRoofNDWoodD = 0x68,
HouseRoofNDWoodE = 0x69,
HouseRoofNDWoodF = 0x6A,
HouseRoofNDWoodG = 0x6B,
HouseRoofNDWoodH = 0x6C,
HouseRoofNDStoneB = 0x6D,
HouseRoofNDStoneC = 0x6E,
HouseRoofNDStoneD = 0x6F,
HouseRoofNDStoneE = 0x70,
HouseRoofNDStoneF = 0x71,
HouseRoofNDStoneG = 0x72,
HouseRoofNDStoneH = 0x73,
HouseRoofNCThatchedB = 0x74,
HouseRoofNCThatchedC = 0x75,
HouseRoofNCThatchedD = 0x76,
HouseRoofNCThatchedE = 0x77,
HouseRoofNCThatchedF = 0x78,
HouseRoofNDThatchedB = 0x79,
HouseRoofNDThatchedC = 0x7A,
HouseRoofNDThatchedD = 0x7B,
HouseRoofNDThatchedE = 0x7C,
HouseRoofNDThatchedF = 0x7D,
HouseRoofPA02StandardB = 0x7E,
HouseRoofPA02StandardC = 0x7F,
HouseRoofPA02StandardD = 0x80,
HouseRoofPA02StandardE = 0x81,
HouseRoofPA02StandardF = 0x82,
HouseRoofPA02StandardG = 0x83,
HouseRoofPA02StandardH = 0x84,
HouseRoofPA03StandardB = 0x85,
HouseRoofPA03StandardC = 0x86,
HouseRoofPA03StandardD = 0x87,
HouseRoofPA03StandardE = 0x88,
HouseRoofPA03StandardF = 0x89,
HouseRoofPA03StandardG = 0x8A,
HouseRoofPA03StandardH = 0x8B,
HouseRoofPA04StandardC = 0x93,
HouseRoofPA04StandardD = 0x94,
HouseRoofPA04StandardE = 0x95,
HouseRoofPA04StandardF = 0x96,
HouseRoofPA04StandardG = 0x97,
HouseRoofPA04StandardH = 0x98,
HouseRoofPA04StoneB = 0x99,
HouseRoofPA04StoneC = 0x9A,
HouseRoofPA04StoneD = 0x9B,
HouseRoofPA04StoneE = 0x9C,
HouseRoofPA04StoneF = 0x9D,
HouseRoofPA04StoneG = 0x9E,
HouseRoofPA04StoneH = 0x9F,
HouseRoofPA04WesterntileB = 0xA0,
HouseRoofPA04WesterntileC = 0xA1,
HouseRoofPA04WesterntileD = 0xA2,
HouseRoofPA04WesterntileE = 0xA3,
HouseRoofPA04WesterntileF = 0xA4,
HouseRoofPA04WesterntileG = 0xA5,
HouseRoofPA04WesterntileH = 0xA6,
HouseRoofPA04ThatchedF = 0xA7,
HouseRoofPA04ThatchedG = 0xA8,
HouseRoofPA04ThatchedH = 0xA9,
HouseRoofNAMetalI = 0xAA,
HouseRoofNBWoodtileI = 0xAB,
HouseRoofNBWoodtileJ = 0xAC,
HouseRoofNASlateI = 0xAD,
HouseRoofNDWoodI = 0xAE,
HouseRoofNDWoodJ = 0xAF,
}
}
HouseRoofPA04StandardA = 0x00,
HouseRoofPA04StandardB = 0x03,
_1 = 0x04,
_2 = 0x05,
HouseRoofPA04ThatchedA = 0x06,
HouseRoofPA04StoneA = 0x07,
HouseRoofPA04WesterntileA = 0x08,
_3 = 0x09,
HouseRoofPA00StandardA = 0x0A,
HouseRoofPA00StandardB = 0x0B,
HouseRoofPA00StandardC = 0x0C,
HouseRoofPA00StandardD = 0x0D,
HouseRoofPA00StandardE = 0x0E,
HouseRoofPA00StandardF = 0x0F,
HouseRoofPA00StandardG = 0x10,
HouseRoofPA00StandardH = 0x11,
_4 = 0x12,
HouseRoofNASlateA = 0x13,
HouseRoofNAMetalA = 0x14,
HouseRoofNATileA = 0x15,
HouseRoofPA04ThatchedB = 0x17,
HouseRoofPA04ThatchedC = 0x18,
HouseRoofPA04ThatchedD = 0x19,
HouseRoofPA04ThatchedE = 0x1A,
HouseRoofNBWoodtileA = 0x1B,
HouseRoofNBThatchedA = 0x1C,
HouseRoofNBWoodpanelA = 0x1D,
HouseRoofNCJapaneseA = 0x1E,
HouseRoofNCThatchedA = 0x1F,
HouseRoofNCStoneweightA = 0x20,
_5 = 0x21,
_6 = 0x22,
HouseRoofNDStoneA = 0x23,
HouseRoofNDThatchedA = 0x24,
HouseRoofNDWoodA = 0x25,
HouseRoofPA01StandardA = 0x26,
HouseRoofPA03StandardA = 0x27,
HouseRoofNATileB = 0x28,
HouseRoofNATileC = 0x29,
HouseRoofNATileD = 0x2A,
HouseRoofNATileE = 0x2B,
HouseRoofNATileF = 0x2C,
HouseRoofNATileG = 0x2D,
HouseRoofNATileH = 0x2E,
HouseRoofPA02StandardA = 0x2F,
HouseRoofNAMetalB = 0x30,
HouseRoofNAMetalC = 0x31,
HouseRoofNAMetalD = 0x32,
HouseRoofNAMetalE = 0x33,
HouseRoofNAMetalF = 0x34,
HouseRoofNAMetalG = 0x35,
HouseRoofNAMetalH = 0x36,
HouseRoofNASlateB = 0x37,
HouseRoofNASlateC = 0x38,
HouseRoofNASlateD = 0x39,
HouseRoofNASlateE = 0x3A,
HouseRoofNASlateF = 0x3B,
HouseRoofNASlateG = 0x3C,
HouseRoofNASlateH = 0x3D,
HouseRoofNBWoodtileB = 0x3E,
HouseRoofNBWoodtileC = 0x3F,
HouseRoofNBWoodtileD = 0x40,
HouseRoofNBWoodtileE = 0x41,
HouseRoofNBWoodtileF = 0x42,
HouseRoofNBWoodtileG = 0x43,
HouseRoofNBWoodtileH = 0x44,
HouseRoofNBWoodpanelB = 0x45,
HouseRoofNBWoodpanelC = 0x46,
HouseRoofNBWoodpanelD = 0x47,
HouseRoofNBWoodpanelE = 0x48,
HouseRoofNBWoodpanelF = 0x49,
HouseRoofNBWoodpanelG = 0x4A,
HouseRoofNBWoodpanelH = 0x4B,
HouseRoofPA01StandardB = 0x4C,
HouseRoofPA01StandardC = 0x4D,
HouseRoofPA01StandardD = 0x4E,
HouseRoofPA01StandardE = 0x4F,
HouseRoofPA01StandardF = 0x50,
HouseRoofPA01StandardG = 0x51,
HouseRoofPA01StandardH = 0x52,
HouseRoofNBThatchedB = 0x53,
HouseRoofNBThatchedC = 0x54,
HouseRoofNBThatchedD = 0x55,
HouseRoofNBThatchedE = 0x56,
HouseRoofNBThatchedF = 0x57,
HouseRoofNBThatchedG = 0x58,
HouseRoofNBThatchedH = 0x59,
HouseRoofNCJapaneseB = 0x5A,
HouseRoofNCJapaneseC = 0x5B,
HouseRoofNCJapaneseD = 0x5C,
HouseRoofNCJapaneseE = 0x5D,
HouseRoofNCJapaneseF = 0x5E,
HouseRoofNCJapaneseG = 0x5F,
HouseRoofNCJapaneseH = 0x60,
HouseRoofNCStoneweightB = 0x61,
HouseRoofNCStoneweightC = 0x62,
HouseRoofNCStoneweightD = 0x63,
HouseRoofNCStoneweightE = 0x64,
HouseRoofNCStoneweightF = 0x65,
HouseRoofNDWoodB = 0x66,
HouseRoofNDWoodC = 0x67,
HouseRoofNDWoodD = 0x68,
HouseRoofNDWoodE = 0x69,
HouseRoofNDWoodF = 0x6A,
HouseRoofNDWoodG = 0x6B,
HouseRoofNDWoodH = 0x6C,
HouseRoofNDStoneB = 0x6D,
HouseRoofNDStoneC = 0x6E,
HouseRoofNDStoneD = 0x6F,
HouseRoofNDStoneE = 0x70,
HouseRoofNDStoneF = 0x71,
HouseRoofNDStoneG = 0x72,
HouseRoofNDStoneH = 0x73,
HouseRoofNCThatchedB = 0x74,
HouseRoofNCThatchedC = 0x75,
HouseRoofNCThatchedD = 0x76,
HouseRoofNCThatchedE = 0x77,
HouseRoofNCThatchedF = 0x78,
HouseRoofNDThatchedB = 0x79,
HouseRoofNDThatchedC = 0x7A,
HouseRoofNDThatchedD = 0x7B,
HouseRoofNDThatchedE = 0x7C,
HouseRoofNDThatchedF = 0x7D,
HouseRoofPA02StandardB = 0x7E,
HouseRoofPA02StandardC = 0x7F,
HouseRoofPA02StandardD = 0x80,
HouseRoofPA02StandardE = 0x81,
HouseRoofPA02StandardF = 0x82,
HouseRoofPA02StandardG = 0x83,
HouseRoofPA02StandardH = 0x84,
HouseRoofPA03StandardB = 0x85,
HouseRoofPA03StandardC = 0x86,
HouseRoofPA03StandardD = 0x87,
HouseRoofPA03StandardE = 0x88,
HouseRoofPA03StandardF = 0x89,
HouseRoofPA03StandardG = 0x8A,
HouseRoofPA03StandardH = 0x8B,
HouseRoofPA04StandardC = 0x93,
HouseRoofPA04StandardD = 0x94,
HouseRoofPA04StandardE = 0x95,
HouseRoofPA04StandardF = 0x96,
HouseRoofPA04StandardG = 0x97,
HouseRoofPA04StandardH = 0x98,
HouseRoofPA04StoneB = 0x99,
HouseRoofPA04StoneC = 0x9A,
HouseRoofPA04StoneD = 0x9B,
HouseRoofPA04StoneE = 0x9C,
HouseRoofPA04StoneF = 0x9D,
HouseRoofPA04StoneG = 0x9E,
HouseRoofPA04StoneH = 0x9F,
HouseRoofPA04WesterntileB = 0xA0,
HouseRoofPA04WesterntileC = 0xA1,
HouseRoofPA04WesterntileD = 0xA2,
HouseRoofPA04WesterntileE = 0xA3,
HouseRoofPA04WesterntileF = 0xA4,
HouseRoofPA04WesterntileG = 0xA5,
HouseRoofPA04WesterntileH = 0xA6,
HouseRoofPA04ThatchedF = 0xA7,
HouseRoofPA04ThatchedG = 0xA8,
HouseRoofPA04ThatchedH = 0xA9,
HouseRoofNAMetalI = 0xAA,
HouseRoofNBWoodtileI = 0xAB,
HouseRoofNBWoodtileJ = 0xAC,
HouseRoofNASlateI = 0xAD,
HouseRoofNDWoodI = 0xAE,
HouseRoofNDWoodJ = 0xAF,
}

View File

@ -1,18 +1,17 @@
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,
SlopeWoodStair = 0x03,
SlopeBrickStair = 0x04,
SlopeReserved = 0x05,
SlopeNatural = 0x1D,
SlopeWoodBlue = 0x1E,
SlopeIronStairBlue = 0x1F,
}
}
SlopeStoneStair = 0x00,
SlopeIronStair = 0x01,
SlopeWood = 0x02,
SlopeWoodStair = 0x03,
SlopeBrickStair = 0x04,
SlopeReserved = 0x05,
SlopeNatural = 0x1D,
SlopeWoodBlue = 0x1E,
SlopeIronStairBlue = 0x1F,
}

View File

@ -1,33 +1,32 @@
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,
InWater = 0x3,
Wind = 0x4,
Plateau = 0x5,
Jungle = 0x6,
Crowd = 0x7,
Cheers = 0x8,
City = 0x9,
Train = 0xA,
Construction = 0xB,
Space = 0xC,
Echo = 0xD,
Storm = 0xE,
Cave = 0x10,
Earthquake = 0x11,
Squeak = 0x12,
Country = 0x14,
Factory = 0x15,
Cyber = 0x1A,
Healing = 0x1B,
Forest = 0x1C,
Duct = 0x1D,
}
}
Silence = 0x0,
Rain = 0x1,
Sea = 0x2,
InWater = 0x3,
Wind = 0x4,
Plateau = 0x5,
Jungle = 0x6,
Crowd = 0x7,
Cheers = 0x8,
City = 0x9,
Train = 0xA,
Construction = 0xB,
Space = 0xC,
Echo = 0xD,
Storm = 0xE,
Cave = 0x10,
Earthquake = 0x11,
Squeak = 0x12,
Country = 0x14,
Factory = 0x15,
Cyber = 0x1A,
Healing = 0x1B,
Forest = 0x1C,
Duct = 0x1D,
}

View File

@ -1,28 +1,27 @@
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()
{
public static Dictionary<string, string[]> GetStructureHelpList()
var kvpa = new[]
{
var kvpa = new[]
{
EnumUtil.GetEnumList<BuildingType>(),
EnumUtil.GetEnumList<BuildingType>(),
EnumUtil.GetEnumList<DoorKind>(),
EnumUtil.GetEnumList<RoofType>(),
EnumUtil.GetEnumList<WallType>(),
EnumUtil.GetEnumList<DoorKind>(),
EnumUtil.GetEnumList<RoofType>(),
EnumUtil.GetEnumList<WallType>(),
EnumUtil.GetEnumList<BridgeType>(),
EnumUtil.GetEnumList<BridgeMaterial>(),
EnumUtil.GetEnumList<SlopeType>(),
};
return kvpa.ToDictionary(x => x.Key, x => x.Value);
}
EnumUtil.GetEnumList<BridgeType>(),
EnumUtil.GetEnumList<BridgeMaterial>(),
EnumUtil.GetEnumList<SlopeType>(),
};
return kvpa.ToDictionary(x => x.Key, x => x.Value);
}
}
}

View File

@ -1,158 +1,157 @@
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,
HouseTentPA = 0x07,
HouseTentNA = 0x08,
HouseWallPA04WoodframeA = 0x09,
HouseWallPA04StoneA = 0x0A,
HouseWallNForSale = 0x0B,
HouseWallNSoldOut = 0x0C,
HouseWallPA00StandardA = 0x0D,
HouseWallNAWoodsidingA = 0x0E,
HouseWallNABrickA = 0x0F,
HouseWallNAStuccoA = 0x10,
HouseWallPA04StuccoA = 0x13,
HouseWallPA04WoodframeB = 0x14,
HouseWallPA04WoodframeC = 0x15,
HouseWallPA04WoodframeD = 0x16,
HouseWallPA04WoodframeE = 0x17,
HouseWallNBStuccoA = 0x18,
HouseWallNBLogA = 0x19,
HouseWallNBStoneA = 0x1A,
HouseWallNCJapaneseA = 0x1B,
HouseWallNCOrientalA = 0x1C,
HouseWallNCWoodA = 0x1D,
HouseWallHouseMovingPA = 0x1E,
HouseWallHouseMovingNA = 0x1F,
HouseWallNDStuccoA = 0x20,
HouseWallNDSoilA = 0x21,
HouseWallNDWoodA = 0x22,
HouseWallPA01StandardA = 0x23,
HouseTentNB = 0x24,
HouseWallPA03StandardA = 0x25,
HouseWallNAStuccoB = 0x2A,
HouseWallNAStuccoC = 0x2B,
HouseWallNAStuccoD = 0x2C,
HouseWallNAStuccoE = 0x2D,
HouseWallNAStuccoF = 0x2E,
HouseWallNAStuccoG = 0x2F,
HouseWallNAStuccoH = 0x30,
HouseWallNABrickB = 0x39,
HouseWallNABrickC = 0x3A,
HouseWallNABrickD = 0x3B,
HouseWallNABrickE = 0x3C,
HouseWallNABrickF = 0x3D,
HouseWallNABrickG = 0x3E,
HouseWallNABrickH = 0x3F,
HouseWallPA02StandardA = 0x40,
HouseWallNAWoodsidingB = 0x41,
HouseWallNAWoodsidingC = 0x42,
HouseWallNAWoodsidingD = 0x43,
HouseWallNAWoodsidingE = 0x44,
HouseWallNAWoodsidingF = 0x45,
HouseWallNAWoodsidingG = 0x46,
HouseWallNAWoodsidingH = 0x47,
HouseWallNBStuccoB = 0x48,
HouseWallNBStuccoC = 0x49,
HouseWallNBStuccoD = 0x4A,
HouseWallNBStuccoE = 0x4B,
HouseWallNBStuccoF = 0x4C,
HouseWallNBStuccoG = 0x4D,
HouseWallNBStuccoH = 0x4E,
HouseWallNBStoneB = 0x4F,
HouseWallNBStoneC = 0x50,
HouseWallNBStoneD = 0x51,
HouseWallNBStoneE = 0x52,
HouseWallNBStoneF = 0x53,
HouseWallNBStoneG = 0x54,
HouseWallNBStoneH = 0x55,
HouseWallNBLogB = 0x56,
HouseWallNBLogC = 0x57,
HouseWallNBLogD = 0x58,
HouseWallNBLogE = 0x59,
HouseWallNBLogF = 0x5A,
HouseWallNBLogG = 0x5B,
HouseWallNBLogH = 0x5C,
HouseWallNCOrientalB = 0x64,
HouseWallNCOrientalC = 0x65,
HouseWallNCOrientalD = 0x66,
HouseWallNCOrientalE = 0x67,
HouseWallNCOrientalF = 0x68,
HouseWallNCOrientalG = 0x69,
HouseWallNCOrientalH = 0x6A,
HouseWallNCJapaneseB = 0x6B,
HouseWallNCJapaneseC = 0x6C,
HouseWallNCJapaneseD = 0x6D,
HouseWallNCJapaneseE = 0x6E,
HouseWallNCJapaneseF = 0x6F,
HouseWallNCJapaneseG = 0x70,
HouseWallNCJapaneseH = 0x71,
HouseWallNCWoodB = 0x72,
HouseWallNCWoodC = 0x73,
HouseWallNCWoodD = 0x74,
HouseWallNCWoodE = 0x75,
HouseWallNCWoodF = 0x76,
HouseWallNCWoodG = 0x77,
HouseWallNCWoodH = 0x78,
HouseWallNDSoilB = 0x79,
HouseWallNDSoilC = 0x7A,
HouseWallNDSoilD = 0x7B,
HouseWallNDSoilE = 0x7C,
HouseWallNDSoilF = 0x7D,
HouseWallNDSoilG = 0x7E,
HouseWallNDSoilH = 0x7F,
HouseWallNDStuccoB = 0x80,
HouseWallNDStuccoC = 0x81,
HouseWallNDStuccoD = 0x82,
HouseWallNDStuccoE = 0x83,
HouseWallNDStuccoF = 0x84,
HouseWallNDStuccoG = 0x85,
HouseWallNDStuccoH = 0x86,
HouseWallNDWoodB = 0x87,
HouseWallNDWoodC = 0x88,
HouseWallNDWoodD = 0x89,
HouseWallNDWoodE = 0x8A,
HouseWallNDWoodF = 0x8B,
HouseWallNDWoodG = 0x8C,
HouseWallNDWoodH = 0x8D,
HouseWallPA04WoodframeF = 0x8E,
HouseWallPA04WoodframeG = 0x8F,
HouseWallPA04WoodframeH = 0x90,
HouseWallPA04StoneB = 0x91,
HouseWallPA04StoneC = 0x92,
HouseWallPA04StoneD = 0x93,
HouseWallPA04StoneE = 0x94,
HouseWallPA04StoneF = 0x95,
HouseWallPA04StoneG = 0x96,
HouseWallPA04StoneH = 0x97,
HouseWallPA04StuccoB = 0x98,
HouseWallPA04StuccoC = 0x99,
HouseWallPA04StuccoD = 0x9A,
HouseWallPA04StuccoE = 0x9B,
HouseWallPA04StuccoF = 0x9C,
HouseWallPA04StuccoG = 0x9D,
HouseWallPA04StuccoH = 0x9E,
HouseWallPA04StandardD = 0x9F,
HouseWallPA04StandardE = 0xA0,
HouseWallPA04StandardF = 0xA1,
HouseWallPA04StandardG = 0xA2,
HouseWallPA04StandardH = 0xA3,
HouseWallNAMetalA = 0xA4,
HouseWallNAMetalB = 0xA5,
HouseWallNAMetalC = 0xA6,
HouseWallNAMetalD = 0xA7,
HouseWallNAMetalE = 0xA8,
HouseWallNAMetalF = 0xA9,
HouseWallNAMetalG = 0xAA,
HouseWallNAMetalH = 0xAB,
HouseWallNAWoodsidingI = 0xAC,
HouseWallNAWoodsidingJ = 0xAD,
}
}
HouseWallPA04StandardA = 0x00,
HouseWallPA04StandardB = 0x05,
HouseWallPA04StandardC = 0x06,
HouseTentPA = 0x07,
HouseTentNA = 0x08,
HouseWallPA04WoodframeA = 0x09,
HouseWallPA04StoneA = 0x0A,
HouseWallNForSale = 0x0B,
HouseWallNSoldOut = 0x0C,
HouseWallPA00StandardA = 0x0D,
HouseWallNAWoodsidingA = 0x0E,
HouseWallNABrickA = 0x0F,
HouseWallNAStuccoA = 0x10,
HouseWallPA04StuccoA = 0x13,
HouseWallPA04WoodframeB = 0x14,
HouseWallPA04WoodframeC = 0x15,
HouseWallPA04WoodframeD = 0x16,
HouseWallPA04WoodframeE = 0x17,
HouseWallNBStuccoA = 0x18,
HouseWallNBLogA = 0x19,
HouseWallNBStoneA = 0x1A,
HouseWallNCJapaneseA = 0x1B,
HouseWallNCOrientalA = 0x1C,
HouseWallNCWoodA = 0x1D,
HouseWallHouseMovingPA = 0x1E,
HouseWallHouseMovingNA = 0x1F,
HouseWallNDStuccoA = 0x20,
HouseWallNDSoilA = 0x21,
HouseWallNDWoodA = 0x22,
HouseWallPA01StandardA = 0x23,
HouseTentNB = 0x24,
HouseWallPA03StandardA = 0x25,
HouseWallNAStuccoB = 0x2A,
HouseWallNAStuccoC = 0x2B,
HouseWallNAStuccoD = 0x2C,
HouseWallNAStuccoE = 0x2D,
HouseWallNAStuccoF = 0x2E,
HouseWallNAStuccoG = 0x2F,
HouseWallNAStuccoH = 0x30,
HouseWallNABrickB = 0x39,
HouseWallNABrickC = 0x3A,
HouseWallNABrickD = 0x3B,
HouseWallNABrickE = 0x3C,
HouseWallNABrickF = 0x3D,
HouseWallNABrickG = 0x3E,
HouseWallNABrickH = 0x3F,
HouseWallPA02StandardA = 0x40,
HouseWallNAWoodsidingB = 0x41,
HouseWallNAWoodsidingC = 0x42,
HouseWallNAWoodsidingD = 0x43,
HouseWallNAWoodsidingE = 0x44,
HouseWallNAWoodsidingF = 0x45,
HouseWallNAWoodsidingG = 0x46,
HouseWallNAWoodsidingH = 0x47,
HouseWallNBStuccoB = 0x48,
HouseWallNBStuccoC = 0x49,
HouseWallNBStuccoD = 0x4A,
HouseWallNBStuccoE = 0x4B,
HouseWallNBStuccoF = 0x4C,
HouseWallNBStuccoG = 0x4D,
HouseWallNBStuccoH = 0x4E,
HouseWallNBStoneB = 0x4F,
HouseWallNBStoneC = 0x50,
HouseWallNBStoneD = 0x51,
HouseWallNBStoneE = 0x52,
HouseWallNBStoneF = 0x53,
HouseWallNBStoneG = 0x54,
HouseWallNBStoneH = 0x55,
HouseWallNBLogB = 0x56,
HouseWallNBLogC = 0x57,
HouseWallNBLogD = 0x58,
HouseWallNBLogE = 0x59,
HouseWallNBLogF = 0x5A,
HouseWallNBLogG = 0x5B,
HouseWallNBLogH = 0x5C,
HouseWallNCOrientalB = 0x64,
HouseWallNCOrientalC = 0x65,
HouseWallNCOrientalD = 0x66,
HouseWallNCOrientalE = 0x67,
HouseWallNCOrientalF = 0x68,
HouseWallNCOrientalG = 0x69,
HouseWallNCOrientalH = 0x6A,
HouseWallNCJapaneseB = 0x6B,
HouseWallNCJapaneseC = 0x6C,
HouseWallNCJapaneseD = 0x6D,
HouseWallNCJapaneseE = 0x6E,
HouseWallNCJapaneseF = 0x6F,
HouseWallNCJapaneseG = 0x70,
HouseWallNCJapaneseH = 0x71,
HouseWallNCWoodB = 0x72,
HouseWallNCWoodC = 0x73,
HouseWallNCWoodD = 0x74,
HouseWallNCWoodE = 0x75,
HouseWallNCWoodF = 0x76,
HouseWallNCWoodG = 0x77,
HouseWallNCWoodH = 0x78,
HouseWallNDSoilB = 0x79,
HouseWallNDSoilC = 0x7A,
HouseWallNDSoilD = 0x7B,
HouseWallNDSoilE = 0x7C,
HouseWallNDSoilF = 0x7D,
HouseWallNDSoilG = 0x7E,
HouseWallNDSoilH = 0x7F,
HouseWallNDStuccoB = 0x80,
HouseWallNDStuccoC = 0x81,
HouseWallNDStuccoD = 0x82,
HouseWallNDStuccoE = 0x83,
HouseWallNDStuccoF = 0x84,
HouseWallNDStuccoG = 0x85,
HouseWallNDStuccoH = 0x86,
HouseWallNDWoodB = 0x87,
HouseWallNDWoodC = 0x88,
HouseWallNDWoodD = 0x89,
HouseWallNDWoodE = 0x8A,
HouseWallNDWoodF = 0x8B,
HouseWallNDWoodG = 0x8C,
HouseWallNDWoodH = 0x8D,
HouseWallPA04WoodframeF = 0x8E,
HouseWallPA04WoodframeG = 0x8F,
HouseWallPA04WoodframeH = 0x90,
HouseWallPA04StoneB = 0x91,
HouseWallPA04StoneC = 0x92,
HouseWallPA04StoneD = 0x93,
HouseWallPA04StoneE = 0x94,
HouseWallPA04StoneF = 0x95,
HouseWallPA04StoneG = 0x96,
HouseWallPA04StoneH = 0x97,
HouseWallPA04StuccoB = 0x98,
HouseWallPA04StuccoC = 0x99,
HouseWallPA04StuccoD = 0x9A,
HouseWallPA04StuccoE = 0x9B,
HouseWallPA04StuccoF = 0x9C,
HouseWallPA04StuccoG = 0x9D,
HouseWallPA04StuccoH = 0x9E,
HouseWallPA04StandardD = 0x9F,
HouseWallPA04StandardE = 0xA0,
HouseWallPA04StandardF = 0xA1,
HouseWallPA04StandardG = 0xA2,
HouseWallPA04StandardH = 0xA3,
HouseWallNAMetalA = 0xA4,
HouseWallNAMetalB = 0xA5,
HouseWallNAMetalC = 0xA6,
HouseWallNAMetalD = 0xA7,
HouseWallNAMetalE = 0xA8,
HouseWallNAMetalF = 0xA9,
HouseWallNAMetalG = 0xAA,
HouseWallNAMetalH = 0xAB,
HouseWallNAWoodsidingI = 0xAC,
HouseWallNAWoodsidingJ = 0xAD,
}

View File

@ -1,152 +1,154 @@
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;
public const int SIZE = 0x2A8; // 3 bytes unused at end
private const int PersonalOffset = 0x38;
private const int PaletteDataStart = 0x78;
public const int PaletteColorCount = 15; // y not 16???
private const int PaletteColorSize = 3; // R, G, B
private const int PixelDataOffset = PaletteDataStart + (PaletteColorCount * PaletteColorSize); // 0xA5
private const int PixelCount = 0x400; // Width * Height
//private const int PixelDataSize = PixelCount / 2; // 4bit|4bit pixel packing
public readonly Memory<byte> Raw;
public Span<byte> Data => Raw.Span;
public DesignPattern(Memory<byte> data) => Raw = data;
public uint Hash
{
public const int Width = 32;
public const int Height = 32;
get => ReadUInt32LittleEndian(Data);
set => WriteUInt32LittleEndian(Data, value);
}
public const int SIZE = 0x2A8; // 3 bytes unused at end
private const int PersonalOffset = 0x38;
private const int PaletteDataStart = 0x78;
public const int PaletteColorCount = 15; // y not 16???
private const int PaletteColorSize = 3; // R, G, B
private const int PixelDataOffset = PaletteDataStart + (PaletteColorCount * PaletteColorSize); // 0xA5
private const int PixelCount = 0x400; // Width * Height
//private const int PixelDataSize = PixelCount / 2; // 4bit|4bit pixel packing
public uint Version
{
get => ReadUInt32LittleEndian(Data[0x04..]);
set => WriteUInt32LittleEndian(Data[0x04..], value);
}
public readonly byte[] Data;
public string DesignName
{
get => StringUtil.GetString(Data, 0x10, 20);
set => StringUtil.GetBytes(value, 20).CopyTo(Data[0x10..]);
}
public DesignPattern(byte[] data) => Data = data;
public uint TownID
{
get => ReadUInt32LittleEndian(Data[PersonalOffset..]);
set => WriteUInt32LittleEndian(Data[PersonalOffset..], value);
}
public uint Hash
public string TownName
{
get => StringUtil.GetString(Data, PersonalOffset + 0x04, 10);
set => StringUtil.GetBytes(value, 10).CopyTo(Data[(PersonalOffset + 0x04)..]);
}
public Span<byte> GetTownIdentity() => Data.Slice(PersonalOffset + 0x00, 4 + 20);
public uint PlayerID
{
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)..]);
}
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"/>.
/// </summary>
/// <param name="index">Pixel index</param>
public int this[int index]
{
get
{
get => BitConverter.ToUInt32(Data, 0x00);
set => BitConverter.GetBytes(value).CopyTo(Data, 0x00);
var ofs = PixelDataOffset + (index / 2);
var val = Data[ofs];
return (index & 1) == 0 ? (val & 0x0F) : (val >> 4);
}
public uint Version
set
{
get => BitConverter.ToUInt32(Data, 0x04);
set => BitConverter.GetBytes(value).CopyTo(Data, 0x04);
}
public string DesignName
{
get => StringUtil.GetString(Data, 0x10, 20);
set => StringUtil.GetBytes(value, 20).CopyTo(Data, 0x10);
}
public uint TownID
{
get => BitConverter.ToUInt32(Data, PersonalOffset);
set => BitConverter.GetBytes(value).CopyTo(Data, PersonalOffset);
}
public string TownName
{
get => StringUtil.GetString(Data, PersonalOffset + 0x04, 10);
set => StringUtil.GetBytes(value, 10).CopyTo(Data, PersonalOffset + 0x04);
}
public 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);
}
public string PlayerName
{
get => StringUtil.GetString(Data, PersonalOffset + 0x20, 10);
set => StringUtil.GetBytes(value, 10).CopyTo(Data, PersonalOffset + 0x20);
}
public 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"/>.
/// </summary>
/// <param name="index">Pixel index</param>
public int this[int index]
{
get
{
var ofs = PixelDataOffset + (index / 2);
var val = Data[ofs];
return (index & 1) == 0 ? (val & 0x0F) : (val >> 4);
}
set
{
var ofs = PixelDataOffset + (index / 2);
var val = Data[ofs];
var update = ((index & 1) == 0)
? (val & 0xF0) | (value & 0xF)
: (value & 0xF) << 4 | (val & 0xF);
Data[ofs] = (byte)update;
}
}
public static int GetPixelIndex(int x, int y) => (y * Height) + x;
public int GetPixel(int x, int y)
{
if ((uint)x >= Width)
throw new ArgumentException($"Argument out of range (0-{Width})", nameof(x));
if ((uint)y >= Height)
throw new ArgumentException($"Argument out of range (0-{Height})", nameof(y));
var index = GetPixelIndex(x, y);
return this[index];
}
public static int GetColorOffset(int index)
{
if ((uint)index >= PaletteColorCount)
throw new ArgumentException($"Argument out of range (0-{PaletteColorCount})", nameof(index));
return PaletteDataStart + (index * PaletteColorSize);
}
/// <summary>
/// Builds a new array with unpacked 32bit pixel data.
/// </summary>
public byte[] GetBitmap()
{
byte[] data = new byte[4 * Width * Height];
for (int i = 0; i < PixelCount; i++)
{
var choice = this[i];
if (choice == PaletteColorCount)
continue; // transparent?
var palette = GetColorOffset(choice);
var ofs = i * 4;
data[ofs + 2] = Data[palette + 0];
data[ofs + 1] = Data[palette + 1];
data[ofs + 0] = Data[palette + 2];
data[ofs + 3] = 0xFF; // opaque
}
return data;
}
/// <summary>
/// Returns a raw slice of data containing the 24bit color pixels.
/// </summary>
public byte[] GetPaletteBitmap()
{
var result = new byte[3 * PaletteColorCount];
for (int i = 0; i < PaletteColorCount; i++)
{
var ofs = PaletteDataStart + (i * 3);
result[(i * 3) + 2] = Data[ofs + 0];
result[(i * 3) + 1] = Data[ofs + 1];
result[(i * 3) + 0] = Data[ofs + 2];
}
return result;
var ofs = PixelDataOffset + (index / 2);
var val = Data[ofs];
var update = ((index & 1) == 0)
? (val & 0xF0) | (value & 0xF)
: ((value & 0xF) << 4) | (val & 0xF);
Data[ofs] = (byte)update;
}
}
}
public static int GetPixelIndex(int x, int y) => (y * Height) + x;
public int GetPixel(int x, int y)
{
if ((uint)x >= Width)
throw new ArgumentException($"Argument out of range (0-{Width})", nameof(x));
if ((uint)y >= Height)
throw new ArgumentException($"Argument out of range (0-{Height})", nameof(y));
var index = GetPixelIndex(x, y);
return this[index];
}
public static int GetColorOffset(int index)
{
if ((uint)index >= PaletteColorCount)
throw new ArgumentException($"Argument out of range (0-{PaletteColorCount})", nameof(index));
return PaletteDataStart + (index * PaletteColorSize);
}
/// <summary>
/// Builds a new array with unpacked 32bit pixel data.
/// </summary>
public byte[] GetBitmap()
{
byte[] data = new byte[4 * Width * Height];
for (int i = 0; i < PixelCount; i++)
{
var choice = this[i];
if (choice == PaletteColorCount)
continue; // transparent?
var palette = GetColorOffset(choice);
var ofs = i * 4;
data[ofs + 2] = Data[palette + 0];
data[ofs + 1] = Data[palette + 1];
data[ofs + 0] = Data[palette + 2];
data[ofs + 3] = 0xFF; // opaque
}
return data;
}
/// <summary>
/// Returns a raw slice of data containing the 24bit color pixels.
/// </summary>
public byte[] GetPaletteBitmap()
{
var result = new byte[3 * PaletteColorCount];
for (int i = 0; i < PaletteColorCount; i++)
{
var ofs = PaletteDataStart + (i * 3);
result[(i * 3) + 2] = Data[ofs + 0];
result[(i * 3) + 1] = Data[ofs + 1];
result[(i * 3) + 0] = Data[ofs + 2];
}
return result;
}
}

View File

@ -1,147 +1,149 @@
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;
public const int SIZE = 0x8A8; // 3 bytes unused at end
public const int SheetCount = 4;
private const int PersonalOffset = 0x38;
private const int PaletteDataStart = 0x78;
public const int PaletteColorCount = 15; // y not 16???
private const int PaletteColorSize = 3; // R, G, B
private const int PixelDataOffset = PaletteDataStart + (PaletteColorCount * PaletteColorSize); // 0xA5
private const int PixelCount = 0x400; // Width * Height
private const int SheetDataSize = PixelCount / 2; // 4bit|4bit pixel packing
public readonly Memory<byte> Raw;
public Span<byte> Data => Raw.Span;
public DesignPatternPRO(Memory<byte> data) => Raw = data;
public uint Hash
{
public const int Width = 32;
public const int Height = 32;
public const int SIZE = 0x8A8; // 3 bytes unused at end
public const int SheetCount = 4;
private const int PersonalOffset = 0x38;
private const int PaletteDataStart = 0x78;
public const int PaletteColorCount = 15; // y not 16???
private const int PaletteColorSize = 3; // R, G, B
private const int PixelDataOffset = PaletteDataStart + (PaletteColorCount * PaletteColorSize); // 0xA5
private const int PixelCount = 0x400; // Width * Height
private const int SheetDataSize = PixelCount / 2; // 4bit|4bit pixel packing
public readonly byte[] Data;
public DesignPatternPRO(byte[] data) => Data = data;
public uint Hash
{
get => BitConverter.ToUInt32(Data, 0x00);
set => BitConverter.GetBytes(value).CopyTo(Data, 0x00);
}
public uint Version
{
get => BitConverter.ToUInt32(Data, 0x04);
set => BitConverter.GetBytes(value).CopyTo(Data, 0x04);
}
public string DesignName
{
get => StringUtil.GetString(Data, 0x10, 20);
set => StringUtil.GetBytes(value, 20).CopyTo(Data, 0x10);
}
public uint TownID
{
get => BitConverter.ToUInt32(Data, PersonalOffset);
set => BitConverter.GetBytes(value).CopyTo(Data, PersonalOffset);
}
public string TownName
{
get => StringUtil.GetString(Data, PersonalOffset + 0x04, 10);
set => StringUtil.GetBytes(value, 10).CopyTo(Data, PersonalOffset + 0x04);
}
public 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);
}
public string PlayerName
{
get => StringUtil.GetString(Data, PersonalOffset + 0x20, 10);
set => StringUtil.GetBytes(value, 10).CopyTo(Data, PersonalOffset + 0x20);
}
public byte[] GetPlayerIdentity() => Data.Slice(PersonalOffset + 0x1C, 4 + 20);
public void SetPixelAtIndex(int sheet, int index, int value)
{
var ofs = PixelDataOffset + (index / 2) + (sheet * SheetDataSize);
var val = Data[ofs];
var update = ((index & 1) == 0)
? (val & 0xF0) | (value & 0xF)
: (value & 0xF) << 4 | (val & 0xF);
Data[ofs] = (byte) update;
}
private int GetPixelAtIndex(int sheet, int index)
{
var ofs = PixelDataOffset + (index / 2) + (sheet * SheetDataSize);
var val = Data[ofs];
return (index & 1) == 0 ? (val & 0x0F) : (val >> 4);
}
public static int GetPixelIndex(int sheet, int x, int y) => (sheet * SheetDataSize) + (y * Height) + x;
public int GetPixel(int sheet, int x, int y)
{
if ((uint)x >= Width)
throw new ArgumentException($"Argument out of range (0-{Width})", nameof(x));
if ((uint)y >= Height)
throw new ArgumentException($"Argument out of range (0-{Height})", nameof(y));
var index = GetPixelIndex(sheet, x, y);
return GetPixelAtIndex(sheet, index);
}
public static int GetColorOffset(int index)
{
if ((uint)index >= PaletteColorCount)
throw new ArgumentException($"Argument out of range (0-{PaletteColorCount})", nameof(index));
return PaletteDataStart + (index * PaletteColorSize);
}
/// <summary>
/// Builds a new array with unpacked 32bit pixel data.
/// </summary>
public byte[] GetBitmap(int sheet)
{
byte[] data = new byte[4 * Width * Height];
for (int i = 0; i < PixelCount; i++)
{
var choice = GetPixelAtIndex(sheet, i);
if (choice == PaletteColorCount)
continue; // transparent?
var palette = GetColorOffset(choice);
var ofs = i * 4;
data[ofs + 2] = Data[palette + 0];
data[ofs + 1] = Data[palette + 1];
data[ofs + 0] = Data[palette + 2];
data[ofs + 3] = 0xFF; // opaque
}
return data;
}
/// <summary>
/// Returns a raw slice of data containing the 24bit color pixels.
/// </summary>
public byte[] GetPaletteBitmap()
{
var result = new byte[3 * PaletteColorCount];
for (int i = 0; i < PaletteColorCount; i++)
{
var ofs = PaletteDataStart + (i * 3);
result[(i * 3) + 2] = Data[ofs + 0];
result[(i * 3) + 1] = Data[ofs + 1];
result[(i * 3) + 0] = Data[ofs + 2];
}
return result;
}
get => ReadUInt32LittleEndian(Data);
set => WriteUInt32LittleEndian(Data, value);
}
}
public uint Version
{
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..]);
}
public uint TownID
{
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)..]);
}
public Span<byte> GetTownIdentity() => Data.Slice(PersonalOffset + 0x00, 4 + 20);
public uint PlayerID
{
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)..]);
}
public Span<byte> GetPlayerIdentity() => Data.Slice(PersonalOffset + 0x1C, 4 + 20);
public void SetPixelAtIndex(int sheet, int index, int value)
{
var ofs = PixelDataOffset + (index / 2) + (sheet * SheetDataSize);
var val = Data[ofs];
var update = ((index & 1) == 0)
? (val & 0xF0) | (value & 0xF)
: (value & 0xF) << 4 | (val & 0xF);
Data[ofs] = (byte) update;
}
private int GetPixelAtIndex(int sheet, int index)
{
var ofs = PixelDataOffset + (index / 2) + (sheet * SheetDataSize);
var val = Data[ofs];
return (index & 1) == 0 ? (val & 0x0F) : (val >> 4);
}
public static int GetPixelIndex(int sheet, int x, int y) => (sheet * SheetDataSize) + (y * Height) + x;
public int GetPixel(int sheet, int x, int y)
{
if ((uint)x >= Width)
throw new ArgumentException($"Argument out of range (0-{Width})", nameof(x));
if ((uint)y >= Height)
throw new ArgumentException($"Argument out of range (0-{Height})", nameof(y));
var index = GetPixelIndex(sheet, x, y);
return GetPixelAtIndex(sheet, index);
}
public static int GetColorOffset(int index)
{
if ((uint)index >= PaletteColorCount)
throw new ArgumentException($"Argument out of range (0-{PaletteColorCount})", nameof(index));
return PaletteDataStart + (index * PaletteColorSize);
}
/// <summary>
/// Builds a new array with unpacked 32bit pixel data.
/// </summary>
public byte[] GetBitmap(int sheet)
{
byte[] data = new byte[4 * Width * Height];
for (int i = 0; i < PixelCount; i++)
{
var choice = GetPixelAtIndex(sheet, i);
if (choice == PaletteColorCount)
continue; // transparent?
var palette = GetColorOffset(choice);
var ofs = i * 4;
data[ofs + 2] = Data[palette + 0];
data[ofs + 1] = Data[palette + 1];
data[ofs + 0] = Data[palette + 2];
data[ofs + 3] = 0xFF; // opaque
}
return data;
}
/// <summary>
/// Returns a raw slice of data containing the 24bit color pixels.
/// </summary>
public byte[] GetPaletteBitmap()
{
var result = new byte[3 * PaletteColorCount];
for (int i = 0; i < PaletteColorCount; i++)
{
var ofs = PaletteDataStart + (i * 3);
result[(i * 3) + 2] = Data[ofs + 0];
result[(i * 3) + 1] = Data[ofs + 1];
result[(i * 3) + 0] = Data[ofs + 2];
}
return result;
}
}

View File

@ -2,279 +2,278 @@
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.
/// Dumps a copy of the <see cref="sav"/>'s files in their decrypted state to the requested <see cref="path"/>.
/// </summary>
/// <seealso cref="GameFileLoader"/>
public static class GameFileDumper
/// <param name="sav">Save Data to dump</param>
/// <param name="path">Path to dump to</param>
public static void Dump(this HorizonSave sav, string path)
{
/// <summary>
/// Dumps a copy of the <see cref="sav"/>'s files in their decrypted state to the requested <see cref="path"/>.
/// </summary>
/// <param name="sav">Save Data to dump</param>
/// <param name="path">Path to dump to</param>
public static void Dump(this HorizonSave sav, string path)
sav.Main.Dump(path);
foreach (var p in sav.Players)
{
sav.Main.Dump(path);
foreach (var p in sav.Players)
{
var dir = Path.Combine(path, p.DirectoryName);
p.Dump(dir);
}
}
/// <summary>
/// Dumps a copy of the <see cref="player"/>'s files in their decrypted state to the requested <see cref="path"/>.
/// </summary>
/// <param name="player">Save Data to dump</param>
/// <param name="path">Path to dump to</param>
public static void Dump(this Player player, string path)
{
foreach (var pair in player)
pair.Dump(path);
}
/// <summary>
/// Dumps a copy of the <see cref="pair"/>'s files in their decrypted state to the requested <see cref="path"/>.
/// </summary>
/// <param name="pair">Save Data to dump</param>
/// <param name="path">Path to dump to</param>
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)
{
Directory.CreateDirectory(path);
var file = Path.Combine(path, name);
File.WriteAllBytes(file, data);
}
/// <summary>
/// Dumps all villager houses to the requested <see cref="path"/>.
/// </summary>
/// <param name="houses"></param>
/// <param name="players"></param>
/// <param name="path">Path to dump to</param>
public static void DumpPlayerHouses(this IReadOnlyList<IPlayerHouse> houses, IReadOnlyList<Player> players, string path)
{
for (int i = 0; i < houses.Count; i++)
{
var filename = i < players.Count ? players[i].Personal.PlayerName : $"House {i}";
houses[i].Dump(filename, path);
}
}
/// <summary>
/// Dumps all villager houses to the requested <see cref="path"/>.
/// </summary>
/// <param name="sav">Save Data to dump from</param>
/// <param name="path">Path to dump to</param>
public static void DumpPlayerHouses(this HorizonSave sav, string path)
{
var count = Math.Min(sav.Players.Length, MainSaveOffsets.PlayerCount);
for (int i = 0; i < count; i++)
{
var p = sav.Players[i];
var h = sav.Main.GetPlayerHouse(i);
h.Dump(path, p.Personal);
}
}
private static void Dump(this IPlayerHouse h, string path, IVillagerOrigin p) => h.Dump(p.PlayerName, path);
private static void Dump(this IPlayerHouse h, string player, string path)
{
var dest = Path.Combine(path, $"{player}.{h.Extension}");
var data = h.Write();
File.WriteAllBytes(dest, data);
}
/// <summary>
/// Dumps all villager houses to the requested <see cref="path"/>.
/// </summary>
/// <param name="sav">Save Data to dump from</param>
/// <param name="path">Path to dump to</param>
public static void DumpVillagerHouses(this MainSave sav, string path)
{
for (int i = 0; i < MainSaveOffsets.VillagerCount; i++)
{
var v = sav.GetVillager(i);
var h = sav.GetVillagerHouse(i);
h.Dump(path, v);
}
}
private static void Dump(this IVillagerHouse h, string path, IVillager v)
{
var name = GameInfo.Strings.GetVillager(v.InternalName);
var dest = Path.Combine(path, $"{name}.{h.Extension}");
var data = h.Write();
File.WriteAllBytes(dest, data);
}
/// <summary>
/// Dumps all villagers to the requested <see cref="path"/>.
/// </summary>
/// <param name="villagers">Data to dump from</param>
/// <param name="path">Path to dump to</param>
public static void Dump(this IEnumerable<IVillager> villagers, string path)
{
foreach (var v in villagers)
v.Dump(path);
}
private static void Dump(this IVillager v, string path)
{
var name = GameInfo.Strings.GetVillager(v.InternalName);
var dest = Path.Combine(path, $"{name}.{v.Extension}");
File.WriteAllBytes(dest, v.Write());
}
/// <summary>
/// Dumps all designs to the requested <see cref="path"/>.
/// </summary>
/// <param name="sav">Save Data to dump from</param>
/// <param name="path">Path to dump to</param>
/// <param name="indexed">Export file names prepended with design index</param>
public static void DumpDesigns(this MainSave sav, string path, bool indexed)
{
for (int i = 0; i < sav.Offsets.PatternCount; i++)
{
var dp = sav.GetDesign(i);
dp.Dump(path, i, indexed);
}
}
/// <summary>
/// Dumps all designs to the requested <see cref="path"/>.
/// </summary>
/// <param name="patterns">Patterns to dump</param>
/// <param name="path">Path to dump to</param>
/// <param name="indexed">Export file names prepended with design index</param>
public static void Dump(this IReadOnlyList<DesignPattern> patterns, string path, bool indexed)
{
for (var index = 0; index < patterns.Count; index++)
{
var dp = patterns[index];
dp.Dump(path, index, indexed);
}
}
private static void Dump(this DesignPattern dp, string path, int index, bool indexed)
{
var name = dp.DesignName;
string fn = indexed
? $"{index:00} - {name}.nhd"
: $"{name}.nhd";
fn = StringUtil.CleanFileName(fn);
var dest = Path.Combine(path, fn);
File.WriteAllBytes(dest, dp.Data);
}
/// <summary>
/// Loads all designs from the requested <see cref="path"/>.
/// </summary>
/// <param name="patterns">Patterns to load</param>
/// <param name="path">Path to load from</param>
/// <param name="changeOrigins">Change origins of Patterns</param>
public static void Load(this DesignPattern[] patterns, string path, bool changeOrigins)
{
if (patterns.Length == 0)
return;
var files = Directory.GetFiles(path, "*.nhd", SearchOption.TopDirectoryOnly);
int ctr = 0;
foreach (var f in files)
{
var fi = new FileInfo(f);
if (fi.Length != DesignPattern.SIZE)
continue;
var data = File.ReadAllBytes(f);
var p = new DesignPattern(data);
if (changeOrigins)
p.ChangeOrigins(patterns[ctr], data);
patterns[ctr] = p;
if (++ctr >= patterns.Length)
break;
}
}
/// <summary>
/// Dumps all designs to the requested <see cref="path"/>.
/// </summary>
/// <param name="sav">Save Data to dump from</param>
/// <param name="path">Path to dump to</param>
/// <param name="indexed">Export file names prepended with design index</param>
public static void DumpDesignsPRO(this MainSave sav, string path, bool indexed)
{
for (int i = 0; i < sav.Offsets.PatternCount; i++)
{
var dp = sav.GetDesignPRO(i);
dp.Dump(path, i, indexed);
}
}
/// <summary>
/// Dumps all designs to the requested <see cref="path"/>.
/// </summary>
/// <param name="patterns">Patterns to dump</param>
/// <param name="path">Path to dump to</param>
/// <param name="indexed">Export file names prepended with design index</param>
public static void Dump(this IReadOnlyList<DesignPatternPRO> patterns, string path, bool indexed)
{
for (var index = 0; index < patterns.Count; index++)
{
var dp = patterns[index];
dp.Dump(path, index, indexed);
}
}
/// <summary>
/// Loads all designs from the requested <see cref="path"/>.
/// </summary>
/// <param name="patterns">Patterns to load</param>
/// <param name="path">Path to load from</param>
/// <param name="changeOrigins">Change origins of Patterns</param>
/// <param name="sortAlpha">Sort the files by file name instead of depending on the Operating System's return order</param>
public static void Load(this DesignPatternPRO[] patterns, string path, bool changeOrigins, bool sortAlpha = true)
{
if (patterns.Length == 0)
return;
var files = Directory.GetFiles(path, "*.nhpd", SearchOption.TopDirectoryOnly);
if (sortAlpha)
Array.Sort(files);
int ctr = 0;
foreach (var f in files)
{
var fi = new FileInfo(f);
if (fi.Length != DesignPatternPRO.SIZE)
continue;
var data = File.ReadAllBytes(f);
var p = new DesignPatternPRO(data);
if (changeOrigins)
p.ChangeOrigins(patterns[ctr], data);
patterns[ctr] = p;
if (++ctr >= patterns.Length)
break;
}
}
private static void Dump(this DesignPatternPRO dp, string path, int index, bool indexed)
{
var name = dp.DesignName;
string fn = indexed
? $"{index:00} - {name}.nhpd"
: $"{name}.nhpd";
fn = StringUtil.CleanFileName(fn);
var dest = Path.Combine(path, fn);
File.WriteAllBytes(dest, dp.Data);
var dir = Path.Combine(path, p.DirectoryName);
p.Dump(dir);
}
}
}
/// <summary>
/// Dumps a copy of the <see cref="player"/>'s files in their decrypted state to the requested <see cref="path"/>.
/// </summary>
/// <param name="player">Save Data to dump</param>
/// <param name="path">Path to dump to</param>
public static void Dump(this Player player, string path)
{
foreach (var pair in player)
pair.Dump(path);
}
/// <summary>
/// Dumps a copy of the <see cref="pair"/>'s files in their decrypted state to the requested <see cref="path"/>.
/// </summary>
/// <param name="pair">Save Data to dump</param>
/// <param name="path">Path to dump to</param>
public static void Dump(this EncryptedFilePair pair, string path)
{
Dump(path, pair.Data, pair.NameData);
}
private static void Dump(string path, ReadOnlySpan<byte> data, string name)
{
Directory.CreateDirectory(path);
var file = Path.Combine(path, name);
File.WriteAllBytes(file, data);
}
/// <summary>
/// Dumps all villager houses to the requested <see cref="path"/>.
/// </summary>
/// <param name="houses"></param>
/// <param name="players"></param>
/// <param name="path">Path to dump to</param>
public static void DumpPlayerHouses(this IReadOnlyList<IPlayerHouse> houses, IReadOnlyList<Player> players, string path)
{
for (int i = 0; i < houses.Count; i++)
{
var filename = i < players.Count ? players[i].Personal.PlayerName : $"House {i}";
houses[i].Dump(filename, path);
}
}
/// <summary>
/// Dumps all villager houses to the requested <see cref="path"/>.
/// </summary>
/// <param name="sav">Save Data to dump from</param>
/// <param name="path">Path to dump to</param>
public static void DumpPlayerHouses(this HorizonSave sav, string path)
{
var count = Math.Min(sav.Players.Length, MainSaveOffsets.PlayerCount);
for (int i = 0; i < count; i++)
{
var p = sav.Players[i];
var h = sav.Main.GetPlayerHouse(i);
h.Dump(path, p.Personal);
}
}
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)
{
var dest = Path.Combine(path, $"{player}.{h.Extension}");
var data = h.Write();
File.WriteAllBytes(dest, data);
}
/// <summary>
/// Dumps all villager houses to the requested <see cref="path"/>.
/// </summary>
/// <param name="sav">Save Data to dump from</param>
/// <param name="path">Path to dump to</param>
public static void DumpVillagerHouses(this MainSave sav, string path)
{
for (int i = 0; i < MainSaveOffsets.VillagerCount; i++)
{
var v = sav.GetVillager(i);
var h = sav.GetVillagerHouse(i);
h.Dump(path, v);
}
}
private static void Dump(this IVillagerHouse h, string path, IVillager v)
{
var name = GameInfo.Strings.GetVillager(v.InternalName);
var dest = Path.Combine(path, $"{name}.{h.Extension}");
var data = h.Write();
File.WriteAllBytes(dest, data);
}
/// <summary>
/// Dumps all villagers to the requested <see cref="path"/>.
/// </summary>
/// <param name="villagers">Data to dump from</param>
/// <param name="path">Path to dump to</param>
public static void Dump(this IEnumerable<IVillager> villagers, string path)
{
foreach (var v in villagers)
v.Dump(path);
}
private static void Dump(this IVillager v, string path)
{
var name = GameInfo.Strings.GetVillager(v.InternalName);
var dest = Path.Combine(path, $"{name}.{v.Extension}");
File.WriteAllBytes(dest, v.Write());
}
/// <summary>
/// Dumps all designs to the requested <see cref="path"/>.
/// </summary>
/// <param name="sav">Save Data to dump from</param>
/// <param name="path">Path to dump to</param>
/// <param name="indexed">Export file names prepended with design index</param>
public static void DumpDesigns(this MainSave sav, string path, bool indexed)
{
for (int i = 0; i < sav.Offsets.PatternCount; i++)
{
var dp = sav.GetDesign(i);
dp.Dump(path, i, indexed);
}
}
/// <summary>
/// Dumps all designs to the requested <see cref="path"/>.
/// </summary>
/// <param name="patterns">Patterns to dump</param>
/// <param name="path">Path to dump to</param>
/// <param name="indexed">Export file names prepended with design index</param>
public static void Dump(this IReadOnlyList<DesignPattern> patterns, string path, bool indexed)
{
for (var index = 0; index < patterns.Count; index++)
{
var dp = patterns[index];
dp.Dump(path, index, indexed);
}
}
private static void Dump(this DesignPattern dp, string path, int index, bool indexed)
{
var name = dp.DesignName;
string fn = indexed
? $"{index:00} - {name}.nhd"
: $"{name}.nhd";
fn = StringUtil.CleanFileName(fn);
var dest = Path.Combine(path, fn);
File.WriteAllBytes(dest, dp.Data);
}
/// <summary>
/// Loads all designs from the requested <see cref="path"/>.
/// </summary>
/// <param name="patterns">Patterns to load</param>
/// <param name="path">Path to load from</param>
/// <param name="changeOrigins">Change origins of Patterns</param>
public static void Load(this DesignPattern[] patterns, string path, bool changeOrigins)
{
if (patterns.Length == 0)
return;
var files = Directory.GetFiles(path, "*.nhd", SearchOption.TopDirectoryOnly);
int ctr = 0;
foreach (var f in files)
{
var fi = new FileInfo(f);
if (fi.Length != DesignPattern.SIZE)
continue;
var data = File.ReadAllBytes(f);
var p = new DesignPattern(data);
if (changeOrigins)
p.ChangeOrigins(patterns[ctr], data);
patterns[ctr] = p;
if (++ctr >= patterns.Length)
break;
}
}
/// <summary>
/// Dumps all designs to the requested <see cref="path"/>.
/// </summary>
/// <param name="sav">Save Data to dump from</param>
/// <param name="path">Path to dump to</param>
/// <param name="indexed">Export file names prepended with design index</param>
public static void DumpDesignsPRO(this MainSave sav, string path, bool indexed)
{
for (int i = 0; i < sav.Offsets.PatternCount; i++)
{
var dp = sav.GetDesignPRO(i);
dp.Dump(path, i, indexed);
}
}
/// <summary>
/// Dumps all designs to the requested <see cref="path"/>.
/// </summary>
/// <param name="patterns">Patterns to dump</param>
/// <param name="path">Path to dump to</param>
/// <param name="indexed">Export file names prepended with design index</param>
public static void Dump(this IReadOnlyList<DesignPatternPRO> patterns, string path, bool indexed)
{
for (var index = 0; index < patterns.Count; index++)
{
var dp = patterns[index];
dp.Dump(path, index, indexed);
}
}
/// <summary>
/// Loads all designs from the requested <see cref="path"/>.
/// </summary>
/// <param name="patterns">Patterns to load</param>
/// <param name="path">Path to load from</param>
/// <param name="changeOrigins">Change origins of Patterns</param>
/// <param name="sortAlpha">Sort the files by file name instead of depending on the Operating System's return order</param>
public static void Load(this DesignPatternPRO[] patterns, string path, bool changeOrigins, bool sortAlpha = true)
{
if (patterns.Length == 0)
return;
var files = Directory.GetFiles(path, "*.nhpd", SearchOption.TopDirectoryOnly);
if (sortAlpha)
Array.Sort(files);
int ctr = 0;
foreach (var f in files)
{
var fi = new FileInfo(f);
if (fi.Length != DesignPatternPRO.SIZE)
continue;
var data = File.ReadAllBytes(f);
var p = new DesignPatternPRO(data);
if (changeOrigins)
p.ChangeOrigins(patterns[ctr], data);
patterns[ctr] = p;
if (++ctr >= patterns.Length)
break;
}
}
private static void Dump(this DesignPatternPRO dp, string path, int index, bool indexed)
{
var name = dp.DesignName;
string fn = indexed
? $"{index:00} - {name}.nhpd"
: $"{name}.nhpd";
fn = StringUtil.CleanFileName(fn);
var dest = Path.Combine(path, fn);
File.WriteAllBytes(dest, dp.Data);
}
}

View File

@ -1,63 +1,63 @@
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.
/// Loads a copy of the <see cref="sav"/>'s files in their decrypted state from the requested <see cref="path"/>.
/// </summary>
/// <seealso cref="GameFileDumper"/>
public static class GameFileLoader
/// <param name="sav">Save Data to load</param>
/// <param name="path">Path to load from</param>
public static void Load(this HorizonSave sav, string path)
{
/// <summary>
/// Loads a copy of the <see cref="sav"/>'s files in their decrypted state from the requested <see cref="path"/>.
/// </summary>
/// <param name="sav">Save Data to load</param>
/// <param name="path">Path to load from</param>
public static void Load(this HorizonSave sav, string path)
sav.Main.Load(path);
foreach (var p in sav.Players)
{
sav.Main.Load(path);
foreach (var p in sav.Players)
{
var dir = Path.Combine(path, p.DirectoryName);
p.Load(dir);
}
}
/// <summary>
/// Loads a copy of the <see cref="player"/>'s files in their decrypted state from the requested <see cref="path"/>.
/// </summary>
/// <param name="player">Save Data to load</param>
/// <param name="path">Path to load from</param>
public static void Load(this Player player, string path)
{
foreach (var pair in player)
pair.Load(path);
}
/// <summary>
/// Loads the <see cref="pair"/>'s files in their decrypted state from the requested <see cref="path"/>.
/// </summary>
/// <param name="pair">Save Data to load</param>
/// <param name="path">Path to load from</param>
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)
{
if (!Directory.Exists(path))
return;
var file = Path.Combine(path, name);
if (!File.Exists(file))
return;
var import = File.ReadAllBytes(file);
if (data.Length != import.Length)
return;
import.CopyTo(data, 0);
var dir = Path.Combine(path, p.DirectoryName);
p.Load(dir);
}
}
/// <summary>
/// Loads a copy of the <see cref="player"/>'s files in their decrypted state from the requested <see cref="path"/>.
/// </summary>
/// <param name="player">Save Data to load</param>
/// <param name="path">Path to load from</param>
public static void Load(this Player player, string path)
{
foreach (var pair in player)
pair.Load(path);
}
/// <summary>
/// Loads the <see cref="pair"/>'s files in their decrypted state from the requested <see cref="path"/>.
/// </summary>
/// <param name="pair">Save Data to load</param>
/// <param name="path">Path to load from</param>
public static void Load(this EncryptedFilePair pair, string path)
{
Load(path, pair.Data, pair.NameData);
}
private static void Load(string path, Span<byte> data, string name)
{
if (!Directory.Exists(path))
return;
var file = Path.Combine(path, name);
if (!File.Exists(file))
return;
var import = File.ReadAllBytes(file);
if (data.Length != import.Length)
return;
import.CopyTo(data);
}
}

View File

@ -1,442 +1,454 @@
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[]
{
2213, // apple
2287, // Cherry
2214, // Orange
2286, // Peach
2285, // Pear
};
public static ReadOnlySpan<ushort> Fruits =>
[
2213, // apple
2287, // Cherry
2214, // Orange
2286, // Peach
2285 // Pear
];
public static readonly IReadOnlyList<ushort> Bugs = new ushort[]
{
00582, // brown cicada
00583, // tiger butterfly
00584, // Rajah Brooke's birdwing
00585, // red dragonfly
00586, // Queen Alexandra's birdwing
00587, // pondskater
00588, // ant
00590, // pill bug
00591, // wharf roach
00592, // moth
00594, // diving beetle
00595, // darner dragonfly
00596, // goliath beetle
00597, // fly
00598, // orchid mantis
00599, // tiger beetle
00600, // horned hercules
00601, // evening cicada
00602, // cyclommatus stag
00603, // firefly
00604, // dung beetle
00605, // rice grasshopper
00606, // mosquito
00607, // mantis
00608, // stinkbug
00609, // citrus long-horned beetle
00610, // peacock butterfly
00611, // snail
00612, // horned dynastid
00613, // grasshopper
00614, // earth-boring dung beetle
00615, // horned atlas
00616, // walking leaf
00617, // cricket
00618, // giant cicada
00619, // spider
00620, // agrias butterfly
00621, // robust cicada
00622, // bagworm
00623, // honeybee
00624, // miyama stag
00625, // yellow butterfly
00626, // common butterfly
00627, // emperor butterfly
00628, // centipede
00630, // walking stick
00631, // rainbow stag
00632, // saw stag
00633, // flea
00634, // mole cricket
00635, // banded dragonfly
00636, // monarch butterfly
00637, // giant stag
00638, // golden stag
00639, // scarab beetle
00640, // scorpion
00641, // cicada shell
00642, // bell cricket
00643, // wasp
00644, // long locust
00645, // jewel beetle
00646, // tarantula
00647, // ladybug
00648, // migratory locust
00649, // walker cicada
00650, // violin beetle
00651, // hermit crab
00652, // Atlas moth
00653, // horned elephant
03477, // common bluebottle
03478, // paper kite butterfly
03479, // great purple emperor
03480, // drone beetle
03482, // giraffe stag
03483, // man-faced stink bug
03484, // Madagascan sunset moth
03485, // blue weevil beetle
03487, // rosalia batesi beetle
03539, // snowflake
03540, // large snowflake
04702, // Wisp spirit piece
05157, // giant water bug
05339, // damselfly
05859, // cherry-blossom petal
07374, // maple leaf
};
public static ReadOnlySpan<ushort> Bugs =>
[
00582, // brown cicada
00583, // tiger butterfly
00584, // Rajah Brooke's birdwing
00585, // red dragonfly
00586, // Queen Alexandra's birdwing
00587, // pondskater
00588, // ant
00590, // pill bug
00591, // wharf roach
00592, // moth
00594, // diving beetle
00595, // darner dragonfly
00596, // goliath beetle
00597, // fly
00598, // orchid mantis
00599, // tiger beetle
00600, // horned hercules
00601, // evening cicada
00602, // cyclommatus stag
00603, // firefly
00604, // dung beetle
00605, // rice grasshopper
00606, // mosquito
00607, // mantis
00608, // stinkbug
00609, // citrus long-horned beetle
00610, // peacock butterfly
00611, // snail
00612, // horned dynastid
00613, // grasshopper
00614, // earth-boring dung beetle
00615, // horned atlas
00616, // walking leaf
00617, // cricket
00618, // giant cicada
00619, // spider
00620, // agrias butterfly
00621, // robust cicada
00622, // bagworm
00623, // honeybee
00624, // miyama stag
00625, // yellow butterfly
00626, // common butterfly
00627, // emperor butterfly
00628, // centipede
00630, // walking stick
00631, // rainbow stag
00632, // saw stag
00633, // flea
00634, // mole cricket
00635, // banded dragonfly
00636, // monarch butterfly
00637, // giant stag
00638, // golden stag
00639, // scarab beetle
00640, // scorpion
00641, // cicada shell
00642, // bell cricket
00643, // wasp
00644, // long locust
00645, // jewel beetle
00646, // tarantula
00647, // ladybug
00648, // migratory locust
00649, // walker cicada
00650, // violin beetle
00651, // hermit crab
00652, // Atlas moth
00653, // horned elephant
03477, // common bluebottle
03478, // paper kite butterfly
03479, // great purple emperor
03480, // drone beetle
03482, // giraffe stag
03483, // man-faced stink bug
03484, // Madagascan sunset moth
03485, // blue weevil beetle
03487, // rosalia batesi beetle
03539, // snowflake
03540, // large snowflake
04702, // Wisp spirit piece
05157, // giant water bug
05339, // damselfly
05859, // cherry-blossom petal
07374 // maple leaf
];
public static readonly IReadOnlyList<ushort> Fish = new ushort[]
{
00328, // crucian carp
00329, // goldfish
02215, // bitterling
02216, // pale chub
02217, // dace
02219, // carp
02220, // koi
02221, // pop-eyed goldfish
02222, // killifish
02223, // crawfish
02224, // soft-shelled turtle
02225, // tadpole
02226, // frog
02227, // freshwater goby
02228, // loach
02229, // catfish
02231, // giant snakehead
02232, // bluegill
02233, // yellow perch
02234, // black bass
02235, // pike
02236, // pond smelt
02237, // sweetfish
02238, // cherry salmon
02239, // char
02241, // stringfish
02242, // salmon
02243, // king salmon
02244, // mitten crab
02245, // guppy
02246, // nibble fish
02247, // angelfish
02248, // neon tetra
02249, // piranha
02250, // arowana
02251, // dorado
02252, // gar
02253, // arapaima
02254, // saddled bichir
02255, // sea butterfly
02256, // sea horse
02257, // clown fish
02258, // surgeonfish
02259, // butterfly fish
02260, // Napoleonfish
02261, // zebra turkeyfish
02262, // blowfish
02263, // puffer fish
02264, // horse mackerel
02265, // barred knifejaw
02266, // sea bass
02267, // red snapper
02268, // dab
02269, // olive flounder
02270, // squid
02271, // moray eel
02272, // ribbon eel
02273, // football fish
02274, // tuna
02275, // blue marlin
02276, // giant trevally
02277, // ray
02278, // ocean sunfish
02279, // hammerhead shark
02280, // great white shark
02281, // saw shark
02282, // whale shark
02283, // oarfish
02284, // coelacanth
02502, // stone
03466, // empty can
03469, // boot
03470, // old tire
04189, // sturgeon
04190, // tilapia
04191, // betta
04192, // snapping turtle
04193, // golden trout
04194, // rainbowfish
04201, // anchovy
04202, // mahi-mahi
04203, // suckerfish
04204, // barreleye
05254, // ranchu goldfish
12514, // water egg
};
public static ReadOnlySpan<ushort> Fish =>
[
00328, // crucian carp
00329, // goldfish
02215, // bitterling
02216, // pale chub
02217, // dace
02219, // carp
02220, // koi
02221, // pop-eyed goldfish
02222, // killifish
02223, // crawfish
02224, // soft-shelled turtle
02225, // tadpole
02226, // frog
02227, // freshwater goby
02228, // loach
02229, // catfish
02231, // giant snakehead
02232, // bluegill
02233, // yellow perch
02234, // black bass
02235, // pike
02236, // pond smelt
02237, // sweetfish
02238, // cherry salmon
02239, // char
02241, // stringfish
02242, // salmon
02243, // king salmon
02244, // mitten crab
02245, // guppy
02246, // nibble fish
02247, // angelfish
02248, // neon tetra
02249, // piranha
02250, // arowana
02251, // dorado
02252, // gar
02253, // arapaima
02254, // saddled bichir
02255, // sea butterfly
02256, // sea horse
02257, // clown fish
02258, // surgeonfish
02259, // butterfly fish
02260, // Napoleonfish
02261, // zebra turkeyfish
02262, // blowfish
02263, // puffer fish
02264, // horse mackerel
02265, // barred knifejaw
02266, // sea bass
02267, // red snapper
02268, // dab
02269, // olive flounder
02270, // squid
02271, // moray eel
02272, // ribbon eel
02273, // football fish
02274, // tuna
02275, // blue marlin
02276, // giant trevally
02277, // ray
02278, // ocean sunfish
02279, // hammerhead shark
02280, // great white shark
02281, // saw shark
02282, // whale shark
02283, // oarfish
02284, // coelacanth
02502, // stone
03466, // empty can
03469, // boot
03470, // old tire
04189, // sturgeon
04190, // tilapia
04191, // betta
04192, // snapping turtle
04193, // golden trout
04194, // rainbowfish
04201, // anchovy
04202, // mahi-mahi
04203, // suckerfish
04204, // barreleye
05254, // ranchu goldfish
12514 // water egg
];
public static readonly IReadOnlyList<ushort> Fossils = new ushort[]
{
00169, // ankylo skull
00170, // ankylo torso
00171, // ankylo tail
00177, // archelon skull
00178, // archelon tail
00180, // megacero skull
00181, // megacero torso
00182, // megacero tail
00184, // dimetrodon skull
00185, // dimetrodon torso
00188, // iguanodon skull
00189, // iguanodon torso
00190, // iguanodon tail
00192, // ophthalmo skull
00193, // ophthalmo torso
00195, // mammoth skull
00196, // mammoth torso
00198, // pachy skull
00199, // pachy tail
00202, // parasaur skull
00203, // parasaur torso
00204, // parasaur tail
00206, // ptera body
00207, // right ptera wing
00208, // left ptera wing
00210, // deinony torso
00211, // deinony tail
00213, // sabertooth skull
00214, // sabertooth tail
00216, // diplo skull
00217, // diplo neck
00218, // diplo chest
00219, // diplo pelvis
00220, // diplo tail
00222, // spino skull
00223, // spino torso
00224, // spino tail
00226, // stego skull
00227, // stego torso
00228, // stego tail
00234, // plesio skull
00235, // plesio torso
00236, // plesio tail
00238, // T. rex skull
00239, // T. rex torso
00240, // T. rex tail
00242, // tricera skull
00243, // tricera torso
00244, // tricera tail
00294, // amber
00295, // ammonite
00296, // coprolite
00298, // archaeopteryx
00300, // dinosaur track
00301, // australopith
00302, // shark-tooth pattern
00303, // trilobite
04651, // anomalocaris
04658, // right megalo side
04659, // left megalo side
04660, // dunkleosteus
04662, // myllokunmingia
04663, // eusthenopteron
04664, // acanthostega
04665, // juramaia
04688, // brachio skull
04689, // brachio chest
04690, // brachio pelvis
04691, // brachio tail
04697, // quetzal torso
04698, // right quetzal wing
04699, // left quetzal wing
07251, // diplo tail tip
};
public static ReadOnlySpan<ushort> Fossils =>
[
00169, // ankylo skull
00170, // ankylo torso
00171, // ankylo tail
00177, // archelon skull
00178, // archelon tail
00180, // megacero skull
00181, // megacero torso
00182, // megacero tail
00184, // dimetrodon skull
00185, // dimetrodon torso
00188, // iguanodon skull
00189, // iguanodon torso
00190, // iguanodon tail
00192, // ophthalmo skull
00193, // ophthalmo torso
00195, // mammoth skull
00196, // mammoth torso
00198, // pachy skull
00199, // pachy tail
00202, // parasaur skull
00203, // parasaur torso
00204, // parasaur tail
00206, // ptera body
00207, // right ptera wing
00208, // left ptera wing
00210, // deinony torso
00211, // deinony tail
00213, // sabertooth skull
00214, // sabertooth tail
00216, // diplo skull
00217, // diplo neck
00218, // diplo chest
00219, // diplo pelvis
00220, // diplo tail
00222, // spino skull
00223, // spino torso
00224, // spino tail
00226, // stego skull
00227, // stego torso
00228, // stego tail
00234, // plesio skull
00235, // plesio torso
00236, // plesio tail
00238, // T. rex skull
00239, // T. rex torso
00240, // T. rex tail
00242, // tricera skull
00243, // tricera torso
00244, // tricera tail
00294, // amber
00295, // ammonite
00296, // coprolite
00298, // archaeopteryx
00300, // dinosaur track
00301, // australopith
00302, // shark-tooth pattern
00303, // trilobite
04651, // anomalocaris
04658, // right megalo side
04659, // left megalo side
04660, // dunkleosteus
04662, // myllokunmingia
04663, // eusthenopteron
04664, // acanthostega
04665, // juramaia
04688, // brachio skull
04689, // brachio chest
04690, // brachio pelvis
04691, // brachio tail
04697, // quetzal torso
04698, // right quetzal wing
04699, // left quetzal wing
07251 // diplo tail tip
];
public static readonly IReadOnlyList<ushort> Art = new ushort[]
{
00002, // scenic painting
00005, // graceful painting (forgery)
00006, // graceful painting
00009, // quaint painting (forgery)
00010, // quaint painting
00013, // basic painting (forgery)
00014, // basic painting
00017, // famous painting (forgery)
00018, // famous painting
00020, // perfect painting
00023, // serene painting (forgery)
00024, // serene painting
00027, // wistful painting (forgery)
00028, // wistful painting
00031, // moving painting (forgery)
00032, // moving painting
00034, // warm painting
00038, // dynamic painting
00041, // jolly painting (forgery)
00042, // jolly painting
00044, // common painting
00046, // proper painting
00048, // nice painting
00050, // flowery painting
00052, // moody painting
00055, // amazing painting (forgery)
00056, // amazing painting
00065, // scary painting (forgery)
00066, // scary painting
00068, // worthy painting
00071, // solemn painting (forgery)
00072, // solemn painting
00075, // wild painting right half (forgery)
00076, // wild painting right half
00078, // calm painting
01331, // motherly statue
01332, // motherly statue (forgery)
01333, // gallant statue
01334, // gallant statue (forgery)
01335, // robust statue
01336, // robust statue (forgery)
01337, // ancient statue
01338, // ancient statue (forgery)
01339, // great statue
01341, // beautiful statue
01342, // beautiful statue (forgery)
01343, // mystic statue
01344, // mystic statue (forgery)
01345, // valiant statue
01346, // valiant statue (forgery)
12533, // rock-head statue
12534, // rock-head statue (forgery)
12535, // informative statue
12536, // informative statue (forgery)
12537, // tremendous statue
12538, // tremendous statue (forgery)
12539, // warrior statue
12540, // warrior statue (forgery)
12541, // familiar statue
12570, // wild painting left half
12571, // wild painting left half (forgery)
12618, // twinkling painting
12619, // academic painting
12620, // academic painting (forgery)
12621, // sinking painting
12622, // detailed painting
12623, // detailed painting (forgery)
12624, // glowing painting
12625, // mysterious painting
12629, // scenic painting (forgery)
};
public static ReadOnlySpan<ushort> Art =>
[
00002, // scenic painting
00005, // graceful painting (forgery)
00006, // graceful painting
00009, // quaint painting (forgery)
00010, // quaint painting
00013, // basic painting (forgery)
00014, // basic painting
00017, // famous painting (forgery)
00018, // famous painting
00020, // perfect painting
00023, // serene painting (forgery)
00024, // serene painting
00027, // wistful painting (forgery)
00028, // wistful painting
00031, // moving painting (forgery)
00032, // moving painting
00034, // warm painting
00038, // dynamic painting
00041, // jolly painting (forgery)
00042, // jolly painting
00044, // common painting
00046, // proper painting
00048, // nice painting
00050, // flowery painting
00052, // moody painting
00055, // amazing painting (forgery)
00056, // amazing painting
00065, // scary painting (forgery)
00066, // scary painting
00068, // worthy painting
00071, // solemn painting (forgery)
00072, // solemn painting
00075, // wild painting right half (forgery)
00076, // wild painting right half
00078, // calm painting
01331, // motherly statue
01332, // motherly statue (forgery)
01333, // gallant statue
01334, // gallant statue (forgery)
01335, // robust statue
01336, // robust statue (forgery)
01337, // ancient statue
01338, // ancient statue (forgery)
01339, // great statue
01341, // beautiful statue
01342, // beautiful statue (forgery)
01343, // mystic statue
01344, // mystic statue (forgery)
01345, // valiant statue
01346, // valiant statue (forgery)
12533, // rock-head statue
12534, // rock-head statue (forgery)
12535, // informative statue
12536, // informative statue (forgery)
12537, // tremendous statue
12538, // tremendous statue (forgery)
12539, // warrior statue
12540, // warrior statue (forgery)
12541, // familiar statue
12570, // wild painting left half
12571, // wild painting left half (forgery)
12618, // twinkling painting
12619, // academic painting
12620, // academic painting (forgery)
12621, // sinking painting
12622, // detailed painting
12623, // detailed painting (forgery)
12624, // glowing painting
12625, // mysterious painting
12629 // scenic painting (forgery)
];
public static readonly IReadOnlyList<ushort> Dive = new ushort[]
{
02620, // seaweed
02830, // sea grapes
02831, // sea urchin
02832, // acorn barnacle
02833, // oyster
02834, // turban shell
02835, // abalone
02838, // pearl oyster
02839, // scallop
02840, // sea anemone
02841, // sea star
02842, // sea cucumber
02843, // sea slug
02844, // flatworm
02845, // mantis shrimp
02846, // sweet shrimp
02847, // tiger prawn
02848, // spiny lobster
02849, // lobster
02850, // snow crab
02852, // red king crab
02853, // spider crab
02854, // octopus
02855, // spotted garden eel
02856, // chambered nautilus
02857, // horseshoe crab
02858, // giant isopod
06920, // firefly squid
07191, // gazami crab
07203, // vampire squid
07214, // gigas giant clam
07228, // sea pineapple
07245, // moon jellyfish
07252, // umbrella octopus
07267, // slate pencil urchin
07278, // whelk
07303, // sea pig
07308, // Dungeness crab
07318, // Venus' flower basket
07411, // mussel
};
public static ReadOnlySpan<ushort> Dive =>
[
02620, // seaweed
02830, // sea grapes
02831, // sea urchin
02832, // acorn barnacle
02833, // oyster
02834, // turban shell
02835, // abalone
02838, // pearl oyster
02839, // scallop
02840, // sea anemone
02841, // sea star
02842, // sea cucumber
02843, // sea slug
02844, // flatworm
02845, // mantis shrimp
02846, // sweet shrimp
02847, // tiger prawn
02848, // spiny lobster
02849, // lobster
02850, // snow crab
02852, // red king crab
02853, // spider crab
02854, // octopus
02855, // spotted garden eel
02856, // chambered nautilus
02857, // horseshoe crab
02858, // giant isopod
06920, // firefly squid
07191, // gazami crab
07203, // vampire squid
07214, // gigas giant clam
07228, // sea pineapple
07245, // moon jellyfish
07252, // umbrella octopus
07267, // slate pencil urchin
07278, // whelk
07303, // sea pig
07308, // Dungeness crab
07318, // Venus' flower basket
07411 // mussel
];
public static readonly HashSet<ushort> Shells = new()
{
1374, // sea snail
1375, // venus comb
1376, // conch
// 2 unused 1377, 1378
1379, // sand dollar
1380, // coral
1381, // giant clam
1382, // cowrie
public static ReadOnlySpan<ushort> Shells =>
[
1374, // sea snail
1375, // venus comb
1376, // conch
// 2 unused 1377, 1378
1379, // sand dollar
1380, // coral
1381, // giant clam
1382, // cowrie
5982, // summer shell
12968, // pearl
};
5982, // summer shell
12968 // pearl
];
public static readonly HashSet<ushort> Terraforming = new()
{
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> 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 readonly HashSet<ushort> NoCheckReceived = new(Terraforming)
{
Item.DIYRecipe,
public static ReadOnlySpan<ushort> NoCheckReceived =>
[
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
9046, // Vaulting Pole Recipe
9047, // Flimsy Shovel Recipe
9048, // Flimsy Watering Can Recipe
9049, // Top 8 Pop Hairstyles
9050, // Top 8 Cool Hairstyles
9051, // Top 8 Stylish Hair Colors
Item.DIYRecipe,
9221, // Pretty Good Tools Recipes
9046, // Vaulting Pole Recipe
9047, // Flimsy Shovel Recipe
9048, // Flimsy Watering Can Recipe
9049, // Top 8 Pop Hairstyles
9050, // Top 8 Cool Hairstyles
9051, // Top 8 Stylish Hair Colors
10309, // Slingshot Recipe
9221, // Pretty Good Tools Recipes
11140, // Ultimate Pocket Stuffing
10309, // Slingshot Recipe
12294, // Flimsy Axe Recipe
11140, // Ultimate Pocket Stuffing
12327, // Ladder Recipe
};
}
}
12294, // Flimsy Axe Recipe
12327 // Ladder Recipe
];
}

View File

@ -1,55 +1,54 @@
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.
/// Item ID
/// </summary>
public ushort Index { get; }
/// <summary>
/// Internal Name of the Item
/// </summary>
public string Name { get; }
/// <summary>
/// Item ID that the player receives if the item is dug up.
/// </summary>
/// <remarks>Is <see cref="Item.NONE"/> if it cannot be dug up.</remarks>
public readonly ushort Dig;
/// <summary>
/// Item ID that the player receives if the item is dug up.
/// </summary>
/// <remarks>Is <see cref="Item.NONE"/> if it cannot be picked up.</remarks>
public readonly ushort Pick;
/// <summary>
/// Classification of item.
/// </summary>
public readonly FieldItemKind Kind;
public FieldItemDefinition(ushort id, ushort dig, ushort pick, string name, FieldItemKind kind)
{
Index = id;
Dig = dig;
Pick = pick;
Name = name;
Kind = kind;
}
/// <summary>
/// When the field item is picked up, this is the held item ID.
/// </summary>
/// <remarks>
/// These details are extracted from FgMainParam.bcsv
/// If the item cannot be picked up, the <see cref="Index"/> is returned as a fallback rather than <see cref="Item.NONE"/>.
/// </remarks>
public class FieldItemDefinition : INamedValue
{
/// <summary>
/// Item ID
/// </summary>
public ushort Index { get; }
/// <summary>
/// Internal Name of the Item
/// </summary>
public string Name { get; }
/// <summary>
/// Item ID that the player receives if the item is dug up.
/// </summary>
/// <remarks>Is <see cref="Item.NONE"/> if it cannot be dug up.</remarks>
public readonly ushort Dig;
/// <summary>
/// Item ID that the player receives if the item is dug up.
/// </summary>
/// <remarks>Is <see cref="Item.NONE"/> if it cannot be picked up.</remarks>
public readonly ushort Pick;
/// <summary>
/// Classification of item.
/// </summary>
public readonly FieldItemKind Kind;
public FieldItemDefinition(ushort id, ushort dig, ushort pick, string name, FieldItemKind kind)
{
Index = id;
Dig = dig;
Pick = pick;
Name = name;
Kind = kind;
}
/// <summary>
/// When the field item is picked up, this is the held item ID.
/// </summary>
/// <remarks>
/// 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;
}
}
public ushort HeldItemId => Pick != Item.NONE ? Pick : Dig != Item.NONE ? Dig : Index;
}

View File

@ -1,108 +1,110 @@
using static NHSE.Core.FieldItemKind;
namespace NHSE.Core
namespace NHSE.Core;
public enum FieldItemKind : byte
{
public enum FieldItemKind : byte
{
FenceBamboo,
FenceBarbedWire,
FenceChinese,
FenceConcreteBlock,
FenceCorrugatedIron,
FenceCrossedBamboo,
FenceDriedStraw,
FenceEasterEgg,
FenceGardenPegRope,
FenceHalloween,
FenceHorizontalLog,
FenceHorizontalWood,
FenceIce,
FenceIkegaki,
FenceIronAndStone,
FenceJapanese,
FenceJuneBride,
FenceLattice,
FenceLatticeBig,
FenceLog,
FenceLogWall,
FenceMermaid,
FencePark,
FencePegRope,
FenceSandProtection,
FenceSharply,
FenceSteel,
FenceStone,
FenceVerticalWood,
FenceWallRenga,
FenceWoodWhite,
LadderKitA,
LadderKitB,
LadderKitC,
LadderKitD,
PltBushAzalea,
PltBushCamellia,
PltBushHibiscus,
PltBushHolly,
PltBushHydrangea,
PltBushOsmanthus,
PltBushPlumeria,
PltFlwAnemone,
PltFlwCosmos,
PltFlwHyacinth,
PltFlwLily,
PltFlwMum,
PltFlwPansy,
PltFlwRose,
PltFlwRoseGold,
PltFlwTulip,
PltFlwYuri,
PltTreeBamboo,
PltTreeCedar,
PltTreeCedarDeco,
PltTreeOak,
PltTreePalm,
PltVgtCarrot,
PltVgtPotato,
PltVgtPumpkin,
PltVgtSugarcane,
PltVgtTomato,
PltVgtWheat,
PltVine,
PltWeedAut0,
PltWeedAut1,
PltWeedAut2,
PltWeedLight,
PltWeedSmr,
PltWeedSpr,
PltWeedWin0,
PltWeedWin1,
StoneA,
StoneB,
StoneC,
StoneD,
StoneE,
UnitIconHole,
}
FenceBamboo,
FenceBarbedWire,
FenceChinese,
FenceConcreteBlock,
FenceCorrugatedIron,
FenceCrossedBamboo,
FenceDriedStraw,
FenceEasterEgg,
FenceGardenPegRope,
FenceHalloween,
FenceHorizontalLog,
FenceHorizontalWood,
FenceIce,
FenceIkegaki,
FenceIronAndStone,
FenceJapanese,
FenceJuneBride,
FenceLattice,
FenceLatticeBig,
FenceLog,
FenceLogWall,
FenceMermaid,
FencePark,
FencePegRope,
FenceSandProtection,
FenceSharply,
FenceSteel,
FenceStone,
FenceVerticalWood,
FenceWallRenga,
FenceWoodWhite,
LadderKitA,
LadderKitB,
LadderKitC,
LadderKitD,
PltBushAzalea,
PltBushCamellia,
PltBushHibiscus,
PltBushHolly,
PltBushHydrangea,
PltBushOsmanthus,
PltBushPlumeria,
PltFlwAnemone,
PltFlwCosmos,
PltFlwHyacinth,
PltFlwLily,
PltFlwMum,
PltFlwPansy,
PltFlwRose,
PltFlwRoseGold,
PltFlwTulip,
PltFlwYuri,
PltTreeBamboo,
PltTreeCedar,
PltTreeCedarDeco,
PltTreeOak,
PltTreePalm,
PltVgtCarrot,
PltVgtPotato,
PltVgtPumpkin,
PltVgtSugarcane,
PltVgtTomato,
PltVgtWheat,
PltVine,
PltWeedAut0,
PltWeedAut1,
PltWeedAut2,
PltWeedLight,
PltWeedSmr,
PltWeedSpr,
PltWeedWin0,
PltWeedWin1,
StoneA,
StoneB,
StoneC,
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;
}
}
}
}

File diff suppressed because it is too large Load Diff

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