mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-03-21 17:48:28 -05:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66df00d038 | ||
|
|
4784e2de82 | ||
|
|
a43b6a5d32 | ||
|
|
48938c5e14 | ||
|
|
0e097b1fc6 | ||
|
|
56ba92b68b | ||
|
|
3ad376be44 | ||
|
|
ca6fbf024c | ||
|
|
3c1e7bdc6c | ||
|
|
b1dbc6a82b | ||
|
|
94ad477703 | ||
|
|
42496def98 | ||
|
|
8950613422 | ||
|
|
2faa1b10b1 | ||
|
|
ff69f82234 | ||
|
|
3317a8bfda | ||
|
|
94f3937f2f | ||
|
|
a3e0ed29b4 | ||
|
|
d3a4ed6ad3 | ||
|
|
bb363a7a3d | ||
|
|
2f95536b13 | ||
|
|
301a1e7664 | ||
|
|
662c3db7dc | ||
|
|
5b42ff746d | ||
|
|
c7f02bcc20 | ||
|
|
7617f6dfa7 | ||
|
|
8b08f263e5 | ||
|
|
d827cec5a7 | ||
|
|
e5e7cc914c | ||
|
|
ff72af52ad |
|
|
@ -1,6 +1,6 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>26.03.06</Version>
|
||||
<Version>26.03.20</Version>
|
||||
<LangVersion>14</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<NeutralLanguage>en</NeutralLanguage>
|
||||
|
|
|
|||
|
|
@ -342,12 +342,12 @@ private bool ParseLineNature(ReadOnlySpan<char> input, ReadOnlySpan<string> natu
|
|||
return false;
|
||||
|
||||
var nature = (Nature)index;
|
||||
if (!nature.IsFixed())
|
||||
if (!nature.IsFixed)
|
||||
{
|
||||
LogError(NatureUnrecognized, input);
|
||||
return false;
|
||||
}
|
||||
if (Nature != Nature.Random && Nature != nature)
|
||||
if (Nature.IsFixed && Nature != nature)
|
||||
{
|
||||
LogError(NatureAlreadySpecified, input);
|
||||
return false;
|
||||
|
|
@ -627,7 +627,7 @@ private void AddEVs(List<string> result, in BattleTemplateExportSettings setting
|
|||
BattleTemplateToken.EVsAppendNature => GetStringStatsNatureAmp(EVs, 0, nameEVs, Nature),
|
||||
_ => GetStringStats(EVs, 0, nameEVs),
|
||||
};
|
||||
if (token is BattleTemplateToken.EVsAppendNature && Nature.IsFixed())
|
||||
if (token is BattleTemplateToken.EVsAppendNature && Nature.IsFixed)
|
||||
line += $" ({settings.Localization.Strings.natures[(int)Nature]})";
|
||||
result.Add(cfg.Push(BattleTemplateToken.EVs, line));
|
||||
}
|
||||
|
|
@ -1081,7 +1081,7 @@ private bool ParseLineEVs(ReadOnlySpan<char> line, BattleTemplateLocalization lo
|
|||
return false; // invalid line
|
||||
}
|
||||
|
||||
if (Nature != Nature.Random) // specified in a separate Nature line
|
||||
if (Nature.IsFixed) // specified in a separate Nature line
|
||||
LogError(NatureEffortAmpAlreadySpecified, natureName);
|
||||
else
|
||||
Nature = (Nature)natureIndex;
|
||||
|
|
@ -1100,7 +1100,7 @@ private bool ParseLineEVs(ReadOnlySpan<char> line, BattleTemplateLocalization lo
|
|||
result.TreatAmpsAsSpeedNotLast();
|
||||
var ampNature = AdjustNature(result.Plus, result.Minus);
|
||||
success &= ampNature;
|
||||
if (ampNature && currentNature != Nature.Random && currentNature != Nature)
|
||||
if (ampNature && currentNature.IsFixed && currentNature != Nature)
|
||||
{
|
||||
LogError(NatureEffortAmpConflictNature);
|
||||
Nature = currentNature; // revert to original
|
||||
|
|
|
|||
|
|
@ -205,10 +205,10 @@ public ModifyResult TryModify(TObject entity, IEnumerable<StringInstruction> fil
|
|||
return result;
|
||||
}
|
||||
|
||||
private static Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>>[] GetPropertyDictionaries(IReadOnlyList<Type> types, int expectedMax)
|
||||
private static Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>>[] GetPropertyDictionaries(ReadOnlySpan<Type> types, int expectedMax)
|
||||
{
|
||||
var result = new Dictionary<string, PropertyInfo>.AlternateLookup<ReadOnlySpan<char>>[types.Count];
|
||||
for (int i = 0; i < types.Count; i++)
|
||||
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, expectedMax).GetAlternateLookup<ReadOnlySpan<char>>();
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,8 +30,10 @@ public void SetNickname(string nick)
|
|||
pk.ClearNickname();
|
||||
return;
|
||||
}
|
||||
pk.IsNicknamed = true;
|
||||
|
||||
pk.PrepareNickname();
|
||||
pk.Nickname = nick;
|
||||
pk.IsNicknamed = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -137,7 +139,7 @@ public bool SetUnshiny()
|
|||
/// <param name="nature">Desired <see cref="PKM.Nature"/> value to set.</param>
|
||||
public void SetNature(Nature nature)
|
||||
{
|
||||
if (!nature.IsFixed())
|
||||
if (!nature.IsFixed)
|
||||
nature = 0; // default valid
|
||||
|
||||
var format = pk.Format;
|
||||
|
|
|
|||
|
|
@ -1,21 +1,12 @@
|
|||
using System;
|
||||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for defining a manipulation of box data.
|
||||
/// </summary>
|
||||
public abstract class BoxManipBase : IBoxManip
|
||||
public abstract record BoxManipBase(BoxManipType Type, Func<SaveFile, bool> Usable) : IBoxManip
|
||||
{
|
||||
public BoxManipType Type { get; }
|
||||
public Func<SaveFile, bool> Usable { get; }
|
||||
|
||||
protected BoxManipBase(BoxManipType type, Func<SaveFile, bool> usable)
|
||||
{
|
||||
Type = type;
|
||||
Usable = usable;
|
||||
}
|
||||
|
||||
public abstract string GetPrompt(bool all);
|
||||
public abstract string GetFail(bool all);
|
||||
public abstract string GetSuccess(bool all);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace PKHeX.Core;
|
|||
/// <summary>
|
||||
/// Clears contents of boxes by deleting all that satisfy a criteria.
|
||||
/// </summary>
|
||||
public sealed class BoxManipClear(BoxManipType Type, Func<PKM, bool> criteria, Func<SaveFile, bool> Usable) : BoxManipBase(Type, Usable)
|
||||
public sealed record BoxManipClear(BoxManipType Type, Func<PKM, bool> Criteria, Func<SaveFile, bool> Usable) : BoxManipBase(Type, Usable)
|
||||
{
|
||||
public BoxManipClear(BoxManipType Type, Func<PKM, bool> Criteria) : this(Type, Criteria, _ => true) { }
|
||||
|
||||
|
|
@ -18,6 +18,6 @@ public override int Execute(SaveFile sav, BoxManipParam param)
|
|||
var (start, stop, reverse) = param;
|
||||
return sav.ClearBoxes(start, stop, Method);
|
||||
|
||||
bool Method(PKM p) => reverse ^ criteria(p);
|
||||
bool Method(PKM p) => reverse ^ Criteria(p);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace PKHeX.Core;
|
|||
/// <summary>
|
||||
/// Clears contents of boxes by deleting all that satisfy a criteria based on a <see cref="SaveFile"/>.
|
||||
/// </summary>
|
||||
public sealed class BoxManipClearComplex(BoxManipType Type, Func<PKM, SaveFile, bool> criteria, Func<SaveFile, bool> Usable) : BoxManipBase(Type, Usable)
|
||||
public sealed record BoxManipClearComplex(BoxManipType Type, Func<PKM, SaveFile, bool> Criteria, Func<SaveFile, bool> Usable) : BoxManipBase(Type, Usable)
|
||||
{
|
||||
public BoxManipClearComplex(BoxManipType Type, Func<PKM, SaveFile, bool> Criteria) : this(Type, Criteria, _ => true) { }
|
||||
|
||||
|
|
@ -18,6 +18,6 @@ public override int Execute(SaveFile sav, BoxManipParam param)
|
|||
var (start, stop, reverse) = param;
|
||||
return sav.ClearBoxes(start, stop, Method);
|
||||
|
||||
bool Method(PKM p) => reverse ^ criteria(p, sav);
|
||||
bool Method(PKM p) => reverse ^ Criteria(p, sav);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ namespace PKHeX.Core;
|
|||
/// Clears contents of boxes by deleting all but the first duplicate detected.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Base type of the "is duplicate" hash for the duplicate detection.</typeparam>
|
||||
public sealed class BoxManipClearDuplicate<T> : BoxManipBase
|
||||
public sealed record BoxManipClearDuplicate<T> : BoxManipBase
|
||||
{
|
||||
private readonly HashSet<T> HashSet = [];
|
||||
private readonly Func<PKM, bool> Criteria;
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ namespace PKHeX.Core;
|
|||
/// <summary>
|
||||
/// Modifies contents of boxes by using an <see cref="Action"/> to change data.
|
||||
/// </summary>
|
||||
public sealed class BoxManipModify(BoxManipType type, Action<PKM> Action, Func<SaveFile, bool> Usable)
|
||||
: BoxManipBase(type, Usable)
|
||||
public sealed record BoxManipModify(BoxManipType Type, Action<PKM> Action, Func<SaveFile, bool> Usable)
|
||||
: BoxManipBase(Type, Usable)
|
||||
{
|
||||
public BoxManipModify(BoxManipType type, Action<PKM> Action) : this(type, Action, _ => true) { }
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace PKHeX.Core;
|
|||
/// <summary>
|
||||
/// Modifies contents of boxes by using an <see cref="Action"/> (referencing a Save File) to change data.
|
||||
/// </summary>
|
||||
public sealed class BoxManipModifyComplex(BoxManipType Type, Action<PKM, SaveFile> Action, Func<SaveFile, bool> Usable)
|
||||
public sealed record BoxManipModifyComplex(BoxManipType Type, Action<PKM, SaveFile> Action, Func<SaveFile, bool> Usable)
|
||||
: BoxManipBase(Type, Usable)
|
||||
{
|
||||
public BoxManipModifyComplex(BoxManipType Type, Action<PKM, SaveFile> Action) : this(Type, Action, _ => true) { }
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ namespace PKHeX.Core;
|
|||
/// <summary>
|
||||
/// Sorts contents of boxes by using a Sorter to determine the order.
|
||||
/// </summary>
|
||||
public sealed class BoxManipSort(BoxManipType Type, Func<IEnumerable<PKM>, IEnumerable<PKM>> Sorter, Func<SaveFile, bool> Usable) : BoxManipBase(Type, Usable)
|
||||
public sealed record BoxManipSort(BoxManipType Type, Func<IEnumerable<PKM>, IEnumerable<PKM>> Sorter, Func<SaveFile, bool> Usable) : BoxManipBase(Type, Usable)
|
||||
{
|
||||
public BoxManipSort(BoxManipType Type, Func<IEnumerable<PKM>, IEnumerable<PKM>> Sorter) : this(Type, Sorter, _ => true) { }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
|
@ -6,7 +6,7 @@ namespace PKHeX.Core;
|
|||
/// <summary>
|
||||
/// Sorts contents of boxes by using a <see cref="Sorter"/> (referencing a Save File) to determine the order.
|
||||
/// </summary>
|
||||
public sealed class BoxManipSortComplex : BoxManipBase
|
||||
public sealed record BoxManipSortComplex : BoxManipBase
|
||||
{
|
||||
private readonly Func<IEnumerable<PKM>, SaveFile, int, IEnumerable<PKM>> Sorter;
|
||||
public BoxManipSortComplex(BoxManipType type, Func<IEnumerable<PKM>, SaveFile, IEnumerable<PKM>> sorter) : this(type, sorter, _ => true) { }
|
||||
|
|
|
|||
|
|
@ -61,11 +61,11 @@ private static List<SlotInfoMisc> GetExtraSlots2(SAV2 sav)
|
|||
|
||||
private static List<SlotInfoMisc> GetExtraSlots3(SAV3 sav)
|
||||
{
|
||||
if (sav is not SAV3FRLG)
|
||||
if (sav is not SAV3FRLG frlg)
|
||||
return None;
|
||||
return
|
||||
[
|
||||
new(sav.LargeBuffer[0x3C98..], 0) {Type = StorageSlotType.Daycare},
|
||||
new(frlg.LargeBlock.SingleDaycareRoute5, 0) {Type = StorageSlotType.Daycare},
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ public enum StorageSlotType : byte
|
|||
/// Shiny Overworld Cache
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="GameVersion.ZA"/>
|
||||
/// <see cref="GameVersion.ZA"/>
|
||||
/// </remarks>
|
||||
Shiny,
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ public static class NatureUtil
|
|||
/// Checks if the provided <see cref="value"/> is a valid stored <see cref="Nature"/> value.
|
||||
/// </summary>
|
||||
/// <returns>True if value is an actual nature.</returns>
|
||||
public bool IsFixed() => value < Nature.Random;
|
||||
public bool IsFixed => value != Nature.Random;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the provided <see cref="value"/> is a possible mint nature.
|
||||
|
|
@ -63,12 +63,12 @@ public static class NatureUtil
|
|||
/// <remarks>
|
||||
/// The only valid mint natures are those which have a stat amp applied, or neutral nature being Serious.
|
||||
/// </remarks>
|
||||
public bool IsMint() => (value.IsFixed() && (byte)value % 6 != 0) || value == Nature.Serious;
|
||||
public bool IsMint => (value.IsFixed && (byte)value % 6 != 0) || value == Nature.Serious;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the provided <see cref="value"/> is a neutral nature which has no stat amps applied.
|
||||
/// </summary>
|
||||
public bool IsNeutral() => value.IsFixed() && (byte)value % 6 == 0;
|
||||
public bool IsNeutral => value.IsFixed && (byte)value % 6 == 0;
|
||||
|
||||
/// <summary>
|
||||
/// Converts the provided <see cref="value"/> to a neutral nature.
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ namespace PKHeX.Core;
|
|||
|
||||
public sealed class PlayerBag3E : PlayerBag, IPlayerBag3
|
||||
{
|
||||
private const int BaseOffset = 0x0498;
|
||||
|
||||
public override IReadOnlyList<InventoryPouch3> Pouches { get; } = GetPouches(ItemStorage3E.Instance);
|
||||
public override ItemStorage3E Info => ItemStorage3E.Instance;
|
||||
|
||||
|
|
@ -21,7 +19,7 @@ public sealed class PlayerBag3E : PlayerBag, IPlayerBag3
|
|||
new(0x000, 50, 999, info, PCItems),
|
||||
];
|
||||
|
||||
public PlayerBag3E(SAV3E sav) : this(sav.Large[BaseOffset..], sav.SecurityKey) { }
|
||||
public PlayerBag3E(SAV3E sav) : this(sav.LargeBlock.Inventory, sav.SmallBlock.SecurityKey) { }
|
||||
public PlayerBag3E(ReadOnlySpan<byte> data, uint security)
|
||||
{
|
||||
UpdateSecurityKey(security);
|
||||
|
|
@ -29,7 +27,7 @@ public PlayerBag3E(ReadOnlySpan<byte> data, uint security)
|
|||
}
|
||||
|
||||
public override void CopyTo(SaveFile sav) => CopyTo((SAV3E)sav);
|
||||
public void CopyTo(SAV3E sav) => CopyTo(sav.Large[BaseOffset..]);
|
||||
public void CopyTo(SAV3E sav) => CopyTo(sav.LargeBlock.Inventory);
|
||||
public void CopyTo(Span<byte> data) => Pouches.SaveAll(data);
|
||||
|
||||
public override int GetMaxCount(InventoryType type, int itemIndex)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ namespace PKHeX.Core;
|
|||
|
||||
public sealed class PlayerBag3FRLG(bool VC) : PlayerBag, IPlayerBag3
|
||||
{
|
||||
private const int BaseOffset = 0x0298;
|
||||
public override IItemStorage Info => GetInfo(VC);
|
||||
private static IItemStorage GetInfo(bool vc) => vc ? ItemStorage3FRLG_VC.Instance : ItemStorage3FRLG.Instance;
|
||||
public override IReadOnlyList<InventoryPouch3> Pouches { get; } = GetPouches(GetInfo(VC));
|
||||
|
|
@ -21,7 +20,7 @@ public sealed class PlayerBag3FRLG(bool VC) : PlayerBag, IPlayerBag3
|
|||
new(0x000, 30, 999, info, PCItems),
|
||||
];
|
||||
|
||||
public PlayerBag3FRLG(SAV3FRLG sav) : this(sav.Large[BaseOffset..], sav.SecurityKey, sav.IsVirtualConsole) { }
|
||||
public PlayerBag3FRLG(SAV3FRLG sav) : this(sav.LargeBlock.Inventory, sav.SmallBlock.SecurityKey, sav.IsVirtualConsole) { }
|
||||
public PlayerBag3FRLG(ReadOnlySpan<byte> data, uint security, bool vc) : this(vc)
|
||||
{
|
||||
UpdateSecurityKey(security);
|
||||
|
|
@ -29,7 +28,7 @@ public PlayerBag3FRLG(ReadOnlySpan<byte> data, uint security, bool vc) : this(vc
|
|||
}
|
||||
|
||||
public override void CopyTo(SaveFile sav) => CopyTo((SAV3FRLG)sav);
|
||||
public void CopyTo(SAV3FRLG sav) => CopyTo(sav.Large[BaseOffset..]);
|
||||
public void CopyTo(SAV3FRLG sav) => CopyTo(sav.LargeBlock.Inventory);
|
||||
public void CopyTo(Span<byte> data) => Pouches.SaveAll(data);
|
||||
|
||||
public override int GetMaxCount(InventoryType type, int itemIndex)
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ namespace PKHeX.Core;
|
|||
|
||||
public sealed class PlayerBag3RS : PlayerBag
|
||||
{
|
||||
private const int BaseOffset = 0x0498;
|
||||
|
||||
public override IReadOnlyList<InventoryPouch3> Pouches { get; } = GetPouches(ItemStorage3RS.Instance);
|
||||
public override ItemStorage3RS Info => ItemStorage3RS.Instance;
|
||||
|
||||
|
|
@ -21,11 +19,11 @@ public sealed class PlayerBag3RS : PlayerBag
|
|||
new(0x000, 50, 999, info, PCItems),
|
||||
];
|
||||
|
||||
public PlayerBag3RS(SAV3RS sav) : this(sav.Large[BaseOffset..]) { }
|
||||
public PlayerBag3RS(SAV3RS sav) : this(sav.LargeBlock.Inventory) { }
|
||||
public PlayerBag3RS(ReadOnlySpan<byte> data) => Pouches.LoadAll(data);
|
||||
|
||||
public override void CopyTo(SaveFile sav) => CopyTo((SAV3RS)sav);
|
||||
public void CopyTo(SAV3RS sav) => CopyTo(sav.Large[BaseOffset..]);
|
||||
public void CopyTo(SAV3RS sav) => CopyTo(sav.LargeBlock.Inventory);
|
||||
public void CopyTo(Span<byte> data) => Pouches.SaveAll(data);
|
||||
|
||||
public override int GetMaxCount(InventoryType type, int itemIndex)
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ public sealed class ItemStorage3E : IItemStorage
|
|||
public static ReadOnlySpan<ushort> Key =>
|
||||
[
|
||||
// R/S
|
||||
259, 260, 261, 262, 263, 264, 265, 266, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288,
|
||||
259, 260, 261, 262, 263, 264, 265, 266, 268, 269, 270, 271, 272, 273, 274, 275, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288,
|
||||
// FR/LG
|
||||
349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374,
|
||||
370, 371, 372,
|
||||
// E
|
||||
375, 376,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ public sealed class ItemStorage3FRLG : IItemStorage
|
|||
public static ReadOnlySpan<ushort> Key =>
|
||||
[
|
||||
// R/S
|
||||
259, 260, 261, 262, 263, 264, 265, 266, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288,
|
||||
260, 261, 262, 263, 264, 265,
|
||||
// FR/LG
|
||||
349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374,
|
||||
];
|
||||
|
|
@ -55,7 +55,7 @@ public sealed class ItemStorage3FRLG_VC : IItemStorage // TODO VC RSE: delete me
|
|||
|
||||
// Unobtainable
|
||||
044, // Berry Juice
|
||||
|
||||
|
||||
// TODO RSE VC: Remove these
|
||||
046, 047, // Shoal Salt, Shoal Shell
|
||||
048, 049, 050, 051, // Shards
|
||||
|
|
@ -71,7 +71,7 @@ public sealed class ItemStorage3FRLG_VC : IItemStorage // TODO VC RSE: delete me
|
|||
173, // Lansat Berry (Event)
|
||||
174, // Starf Berry (Event)
|
||||
175, // Enigma Berry (Event)
|
||||
|
||||
|
||||
// TODO RSE VC: Remove these
|
||||
179, // BrightPowder
|
||||
180, // White Herb
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ public EncounterCriteria()
|
|||
/// Determines whether a specific Nature is specified in the criteria or if complex nature mutations are allowed.
|
||||
/// </summary>
|
||||
/// <returns>><see langword="true"/> if a Nature is specified or complex nature mutations are allowed; otherwise, <see langword="false"/>.</returns>
|
||||
public bool IsSpecifiedNature() => Nature != Nature.Random || Mutations.IsComplexNature();
|
||||
public bool IsSpecifiedNature() => Nature.IsFixed || Mutations.IsComplexNature();
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a level range is specified in the criteria.
|
||||
|
|
@ -126,6 +126,12 @@ public EncounterCriteria()
|
|||
/// <returns>><see langword="true"/> if an Ability is specified; otherwise, <see langword="false"/>.</returns>
|
||||
public bool IsSpecifiedAbility() => Ability != Any12H;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the shiny value is explicitly specified rather than set to random.
|
||||
/// </summary>
|
||||
/// <returns>><see langword="true"/> if a Shiny is specified; otherwise, <see langword="false"/>.</returns>
|
||||
public bool IsSpecifiedShiny() => Shiny != Shiny.Random;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether all IVs are specified in the criteria.
|
||||
/// </summary>
|
||||
|
|
@ -183,6 +189,20 @@ public int GetCountSpecifiedIVs() => Convert.ToInt32(IV_HP != RandomIV)
|
|||
_ => throw new ArgumentOutOfRangeException(nameof(ability), ability, null),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified shiny properties satisfy the shiny criteria based on the current <see cref="Shiny"/> setting.
|
||||
/// </summary>
|
||||
/// <returns>><see langword="true"/> if the index satisfies the shiny criteria; otherwise, <see langword="false"/>.</returns>
|
||||
public bool IsSatisfiedShiny(uint xor, uint cmp) => Shiny switch
|
||||
{
|
||||
Shiny.Random => true,
|
||||
Shiny.Never => xor > cmp, // not shiny
|
||||
Shiny.AlwaysSquare => xor == 0, // square shiny
|
||||
Shiny.AlwaysStar => xor < cmp && xor != 0, // star shiny
|
||||
Shiny.Always => xor < cmp, // shiny
|
||||
_ => false, // shouldn't be set
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified Nature satisfies the criteria.
|
||||
/// </summary>
|
||||
|
|
@ -191,7 +211,7 @@ public int GetCountSpecifiedIVs() => Convert.ToInt32(IV_HP != RandomIV)
|
|||
public bool IsSatisfiedNature(Nature nature)
|
||||
{
|
||||
if (Mutations.HasFlag(AllowOnlyNeutralNature))
|
||||
return nature.IsNeutral();
|
||||
return nature.IsNeutral;
|
||||
if (Nature == Nature.Random)
|
||||
return true;
|
||||
return nature == Nature || Mutations.HasFlag(CanMintNature);
|
||||
|
|
@ -300,7 +320,7 @@ public Nature GetNature(Nature encValue)
|
|||
/// </summary>
|
||||
public Nature GetNature()
|
||||
{
|
||||
if (Nature != Nature.Random)
|
||||
if (Nature.IsFixed)
|
||||
return Nature;
|
||||
var result = (Nature)Util.Rand.Next(25);
|
||||
if (Mutations.HasFlag(AllowOnlyNeutralNature))
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ public enum PogoType : byte
|
|||
|
||||
// Pokémon captured in the wild.
|
||||
Wild,
|
||||
WildLevel20,
|
||||
WildLevel25,
|
||||
|
||||
// Pokémon hatched from Eggs.
|
||||
Egg,
|
||||
|
|
@ -137,6 +139,8 @@ public static class PogoTypeExtensions
|
|||
public byte LevelMin => encounterType switch
|
||||
{
|
||||
Wild => 1,
|
||||
WildLevel20 => 20,
|
||||
WildLevel25 => 25,
|
||||
Egg => 1,
|
||||
Egg12km => 8,
|
||||
Raid => 20,
|
||||
|
|
@ -204,6 +208,8 @@ public static class PogoTypeExtensions
|
|||
public int MinimumIV => encounterType switch
|
||||
{
|
||||
Wild => 0,
|
||||
WildLevel20 => 0,
|
||||
WildLevel25 => 0,
|
||||
RaidMythical => 10,
|
||||
RaidShadowMythical => 8,
|
||||
RaidShadowMythicalGOWA => 8,
|
||||
|
|
@ -324,6 +330,6 @@ public bool IsBallValid(Ball ball)
|
|||
_ => Ball.None, // Poké, Great, Ultra
|
||||
};
|
||||
|
||||
public bool IsSpecialResearch => encounterType is >= SpecialMythical and < TimedMythical;
|
||||
public bool IsSpecialResearch => encounterType is SpecialResearch or >= SpecialMythical and < TimedMythical;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ private bool IsMatchNatureGenderShiny(PKM pk)
|
|||
return false;
|
||||
if (Gender != FixedGenderUtil.GenderRandom && Gender != pk.Gender)
|
||||
return false;
|
||||
if (Nature != Nature.Random && pk.Nature != Nature)
|
||||
if (Nature.IsFixed && pk.Nature != Nature)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ public bool IsMatchExact(PKM pk, EvoCriteria evo)
|
|||
return false;
|
||||
if (IVs.IsSpecified && !Legal.GetIsFixedIVSequenceValidSkipRand(IVs, pk))
|
||||
return false;
|
||||
if (Nature != Nature.Random && pk.Nature != Nature)
|
||||
if (Nature.IsFixed && pk.Nature != Nature)
|
||||
return false;
|
||||
if (FlawlessIVCount != 0 && pk.FlawlessIVCount < FlawlessIVCount)
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ private bool IsMatchNatureGenderShiny(PKM pk)
|
|||
return false;
|
||||
if (Gender != pk.Gender)
|
||||
return false;
|
||||
if (Nature != Nature.Random && pk.Nature != Nature)
|
||||
if (Nature.IsFixed && pk.Nature != Nature)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ public bool IsMatchExact(PKM pk, EvoCriteria evo)
|
|||
return false;
|
||||
if (IVs.IsSpecified && !Legal.GetIsFixedIVSequenceValidSkipRand(IVs, pk))
|
||||
return false;
|
||||
if (Nature != Nature.Random && pk.Nature != Nature)
|
||||
if (Nature.IsFixed && pk.Nature != Nature)
|
||||
return false;
|
||||
if (FlawlessIVCount != 0 && pk.FlawlessIVCount < FlawlessIVCount)
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ private bool IsMatchNatureGenderShiny(PKM pk)
|
|||
return false;
|
||||
if (Gender != pk.Gender)
|
||||
return false;
|
||||
if (Nature != Nature.Random && pk.Nature != Nature)
|
||||
if (Nature.IsFixed && pk.Nature != Nature)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,8 +122,6 @@ protected virtual void SetPINGA(PK8 pk, in EncounterCriteria criteria, PersonalI
|
|||
}
|
||||
|
||||
FinishCorrelation(pk, seed);
|
||||
if (criteria.IsSpecifiedNature() && criteria.Nature != pk.Nature && criteria.Nature.IsMint())
|
||||
pk.StatNature = criteria.Nature;
|
||||
}
|
||||
|
||||
protected GenerateParam8 GetParam() => GetParam(Info);
|
||||
|
|
|
|||
|
|
@ -57,6 +57,10 @@ protected override void SetPINGA(PK8 pk, in EncounterCriteria criteria, Personal
|
|||
{
|
||||
Span<int> iv = stackalloc int[6];
|
||||
|
||||
// Honor a shiny request only at the end; generate as never-shiny to avoid shiny PID rejection in main RNG method.
|
||||
var isShinyRequested = criteria.Shiny.IsShiny();
|
||||
var iterCriteria = criteria with { Shiny = ShinyMethod };
|
||||
|
||||
int ctr = 0;
|
||||
var rand = new Xoroshiro128Plus(Util.Rand.Rand64());
|
||||
var param = GetParam(pi);
|
||||
|
|
@ -64,23 +68,22 @@ protected override void SetPINGA(PK8 pk, in EncounterCriteria criteria, Personal
|
|||
const int max = 100_000;
|
||||
do
|
||||
{
|
||||
if (TryApply(pk, seed = rand.Next(), iv, param, criteria))
|
||||
if (TryApply(pk, seed = rand.Next(), iv, param, iterCriteria))
|
||||
break;
|
||||
} while (++ctr < max);
|
||||
|
||||
if (ctr == max) // fail
|
||||
{
|
||||
if (!TryApply(pk, seed = rand.Next(), iv, param, criteria.WithoutIVs()))
|
||||
iterCriteria = iterCriteria.WithoutIVs();
|
||||
if (!TryApply(pk, seed = rand.Next(), iv, param, iterCriteria))
|
||||
{
|
||||
var tmp = EncounterCriteria.Unrestricted with { Shiny = ShinyMethod };
|
||||
while (!TryApply(pk, seed = rand.Next(), iv, param, tmp)) { }
|
||||
iterCriteria = EncounterCriteria.Unrestricted with { Shiny = ShinyMethod };
|
||||
while (!TryApply(pk, seed = rand.Next(), iv, param, iterCriteria)) { }
|
||||
}
|
||||
}
|
||||
|
||||
FinishCorrelation(pk, seed);
|
||||
if (criteria.IsSpecifiedNature() && criteria.Nature != pk.Nature && criteria.Nature.IsMint())
|
||||
pk.StatNature = criteria.Nature;
|
||||
if (criteria.Shiny.IsShiny())
|
||||
if (isShinyRequested)
|
||||
pk.PID = ShinyUtil.GetShinyPID(pk.TID16, pk.SID16, pk.PID, ShinyXor);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -209,7 +209,7 @@ private bool IsMatchNatureGenderShiny(PKM pk)
|
|||
{
|
||||
if (!Shiny.IsValid(pk))
|
||||
return false;
|
||||
if (Nature != Nature.Random && pk.Nature != Nature)
|
||||
if (Nature.IsFixed && pk.Nature != Nature)
|
||||
return false;
|
||||
if (Gender != FixedGenderUtil.GenderRandom && pk.Gender != Gender)
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ private void SetPINGA(PK9 pk, in EncounterCriteria criteria, PersonalInfo9SV pi)
|
|||
|
||||
if (Gender != FixedGenderUtil.GenderRandom)
|
||||
pk.Gender = Gender;
|
||||
if (Nature != Nature.Random)
|
||||
if (Nature.IsFixed)
|
||||
pk.Nature = pk.StatNature = Nature;
|
||||
}
|
||||
#endregion
|
||||
|
|
@ -178,7 +178,7 @@ public bool IsMatchExact(PKM pk, EvoCriteria evo)
|
|||
return false;
|
||||
if (TeraType != GemType.Random && pk is ITeraType t && !Tera9RNG.IsMatchTeraType(TeraType, Species, Form, (byte)t.TeraTypeOriginal))
|
||||
return false;
|
||||
if (Nature != Nature.Random && pk.Nature != Nature)
|
||||
if (Nature.IsFixed && pk.Nature != Nature)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ public bool IsMatchExact(PKM pk, EvoCriteria evo)
|
|||
return false;
|
||||
if (FlawlessIVCount != 0 && pk.FlawlessIVCount < FlawlessIVCount)
|
||||
return false;
|
||||
if (Nature != Nature.Random && pk.Nature != Nature)
|
||||
if (Nature.IsFixed && pk.Nature != Nature)
|
||||
return false;
|
||||
if (pk is IAlphaReadOnly a && a.IsAlpha != IsAlpha)
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ public bool IsMatchExact(PKM pk, EvoCriteria evo)
|
|||
return false;
|
||||
if (FlawlessIVCount != 0 && pk.FlawlessIVCount < FlawlessIVCount)
|
||||
return false;
|
||||
if (Nature != Nature.Random && pk.Nature != Nature)
|
||||
if (Nature.IsFixed && pk.Nature != Nature)
|
||||
return false;
|
||||
if (pk is IAlphaReadOnly a && a.IsAlpha != IsAlpha)
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ private bool IsMatchNatureGenderShiny(PKM pk)
|
|||
return false;
|
||||
if (pk.Gender != Gender)
|
||||
return false;
|
||||
if (Nature != Nature.Random && pk.Nature != Nature)
|
||||
if (Nature.IsFixed && pk.Nature != Nature)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -192,7 +192,7 @@ public bool IsMatchExact(PKM pk, EvoCriteria evo)
|
|||
return false;
|
||||
if (FlawlessIVCount != 0 && pk.FlawlessIVCount < FlawlessIVCount)
|
||||
return false;
|
||||
if (Nature != Nature.Random && pk.Nature != Nature)
|
||||
if (Nature.IsFixed && pk.Nature != Nature)
|
||||
return false;
|
||||
if (pk is IAlphaReadOnly a && a.IsAlpha != IsAlpha)
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -36,10 +36,10 @@ public static bool GenerateData(PA9 pk, in GenerateParam9a enc, in EncounterCrit
|
|||
{
|
||||
var rand = new Xoroshiro128Plus(seed);
|
||||
pk.EncryptionConstant = (uint)rand.NextInt(uint.MaxValue);
|
||||
pk.PID = GetAdaptedPID(ref rand, pk, enc);
|
||||
|
||||
if (enc.Shiny is Shiny.Random && criteria.Shiny.IsShiny() != pk.IsShiny)
|
||||
var pid = GetAdaptedPID(ref rand, pk, enc);
|
||||
if (enc.Shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(ShinyUtil.GetShinyXor(pid, pk.ID32), 16))
|
||||
return false;
|
||||
pk.PID = pid;
|
||||
|
||||
Span<int> ivs = [UNSET, UNSET, UNSET, UNSET, UNSET, UNSET];
|
||||
if (enc.IVs.IsSpecified)
|
||||
|
|
@ -104,7 +104,7 @@ public static bool GenerateData(PA9 pk, in GenerateParam9a enc, in EncounterCrit
|
|||
return false;
|
||||
pk.Gender = gender;
|
||||
|
||||
var nature = enc.Nature != Nature.Random ? enc.Nature
|
||||
var nature = enc.Nature.IsFixed ? enc.Nature
|
||||
: (Nature)rand.NextInt(25);
|
||||
|
||||
// Compromise on Nature -- some are fixed, some are random. If the request wants a specific nature, just mint it.
|
||||
|
|
@ -264,7 +264,7 @@ private static bool IsMatchIVsAndFollowing(PKM pk, in GenerateParam9a enc, Xoros
|
|||
if (pk.Gender != gender)
|
||||
return false;
|
||||
|
||||
var nature = enc.Nature != Nature.Random ? enc.Nature
|
||||
var nature = enc.Nature.IsFixed ? enc.Nature
|
||||
: (Nature)rand.NextInt(25);
|
||||
if (pk.Nature != nature)
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -13,5 +13,5 @@ public interface IFixedNature
|
|||
/// <summary>
|
||||
/// Indicates if the nature is a single value (not random).
|
||||
/// </summary>
|
||||
bool IsFixedNature => Nature != Nature.Random;
|
||||
bool IsFixedNature => Nature.IsFixed;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ public static class Legal
|
|||
|
||||
internal const int MaxSpeciesID_3 = 386;
|
||||
internal const int MaxMoveID_3 = 354;
|
||||
internal const int MaxItemID_3 = 374;
|
||||
internal const int MaxItemID_3_RS = 346;
|
||||
internal const int MaxItemID_3_FRLG = 374;
|
||||
internal const int MaxItemID_3_E = 376;
|
||||
internal const int MaxItemID_3_COLO = 547;
|
||||
internal const int MaxItemID_3_XD = 593;
|
||||
|
|
|
|||
|
|
@ -55,7 +55,8 @@ private static bool TryApplyFromSeed(PK8 pk, in EncounterCriteria criteria, Shin
|
|||
if (shiny == Shiny.AlwaysStar && type != Shiny.AlwaysStar)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(GetShinyXor(pid, pk.ID32), 16))
|
||||
return false;
|
||||
pk.PID = pid;
|
||||
|
||||
// IVs
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ public static bool Verify(PKM pk, ulong seed, Span<int> ivs, in GenerateParam8 p
|
|||
break;
|
||||
}
|
||||
|
||||
var nature = param.Nature != Nature.Random ? param.Nature
|
||||
var nature = param.Nature.IsFixed ? param.Nature
|
||||
: param.Species == (int)Species.Toxtricity
|
||||
? ToxtricityUtil.GetRandomNature(ref rng, pk.Form)
|
||||
: (Nature)rng.NextInt(25);
|
||||
|
|
@ -160,6 +160,8 @@ public static bool TryApply(PK8 pk, ulong seed, Span<int> ivs, in GenerateParam8
|
|||
|
||||
var trID = (uint)rng.NextInt();
|
||||
var pid = (uint)rng.NextInt();
|
||||
|
||||
// Battle
|
||||
var xor = GetShinyXor(pid, trID);
|
||||
bool isShiny = xor < 16;
|
||||
if (isShiny && param.Shiny == Shiny.Never)
|
||||
|
|
@ -167,9 +169,8 @@ public static bool TryApply(PK8 pk, ulong seed, Span<int> ivs, in GenerateParam8
|
|||
ForceShinyState(false, ref pid, trID, 0);
|
||||
isShiny = false;
|
||||
}
|
||||
if (param.Shiny is Shiny.Random && isShiny != criteria.Shiny.IsShiny())
|
||||
return false;
|
||||
|
||||
// Captured
|
||||
if (isShiny)
|
||||
{
|
||||
if (!GetIsShiny6(pk.ID32, pid))
|
||||
|
|
@ -181,6 +182,8 @@ public static bool TryApply(PK8 pk, ulong seed, Span<int> ivs, in GenerateParam8
|
|||
pid ^= 0x1000_0000;
|
||||
}
|
||||
|
||||
if (param.Shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(GetShinyXor(pid, pk.ID32), 16))
|
||||
return false;
|
||||
pk.PID = pid;
|
||||
|
||||
const int UNSET = -1;
|
||||
|
|
@ -236,7 +239,7 @@ public static bool TryApply(PK8 pk, ulong seed, Span<int> ivs, in GenerateParam8
|
|||
return false;
|
||||
pk.Gender = gender;
|
||||
|
||||
var nature = param.Nature != Nature.Random ? param.Nature
|
||||
var nature = param.Nature.IsFixed ? param.Nature
|
||||
: param.Species == (int)Species.Toxtricity
|
||||
? ToxtricityUtil.GetRandomNature(ref rng, pk.Form)
|
||||
: (Nature)rng.NextInt(25);
|
||||
|
|
|
|||
|
|
@ -129,6 +129,8 @@ public static bool TryApplyFromSeed(PA8 pk, in EncounterCriteria criteria, in Ov
|
|||
if (para.Shiny == Shiny.AlwaysStar && type != Shiny.AlwaysStar)
|
||||
return false;
|
||||
}
|
||||
if (para.Shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(GetShinyXor(pid, pk.ID32), 16))
|
||||
return false;
|
||||
pk.PID = pid;
|
||||
|
||||
Span<int> ivs = [UNSET, UNSET, UNSET, UNSET, UNSET, UNSET];
|
||||
|
|
@ -172,6 +174,8 @@ public static bool TryApplyFromSeed(PA8 pk, in EncounterCriteria criteria, in Ov
|
|||
pk.Gender = gender;
|
||||
|
||||
var nature = (Nature)rand.NextInt(25);
|
||||
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature(nature))
|
||||
return false;
|
||||
pk.Nature = pk.StatNature = nature;
|
||||
|
||||
var (height, weight) = para.IsAlpha
|
||||
|
|
@ -179,16 +183,10 @@ public static bool TryApplyFromSeed(PA8 pk, in EncounterCriteria criteria, in Ov
|
|||
: ((byte)(rand.NextInt(0x81) + rand.NextInt(0x80)),
|
||||
(byte)(rand.NextInt(0x81) + rand.NextInt(0x80)));
|
||||
|
||||
if (pk is IScaledSize s)
|
||||
{
|
||||
s.HeightScalar = height;
|
||||
s.WeightScalar = weight;
|
||||
if (pk is IScaledSizeValue a)
|
||||
{
|
||||
a.ResetHeight();
|
||||
a.ResetWeight();
|
||||
}
|
||||
}
|
||||
pk.HeightScalar = height;
|
||||
pk.WeightScalar = weight;
|
||||
pk.ResetHeight();
|
||||
pk.ResetWeight();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,6 +65,8 @@ public static bool TryApplyFromSeed(PB8 pk, in EncounterCriteria criteria, Shiny
|
|||
if (shiny == Shiny.AlwaysStar && type != Shiny.AlwaysStar)
|
||||
return false;
|
||||
}
|
||||
if (shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(GetShinyXor(pid, pk.ID32), 16))
|
||||
return false;
|
||||
pk.PID = pid;
|
||||
|
||||
// Check IVs: Create flawless IVs at random indexes, then the random IVs for not flawless.
|
||||
|
|
|
|||
|
|
@ -65,6 +65,8 @@ public static bool TryApplyFromSeed(PB8 pk, in EncounterCriteria criteria, Shiny
|
|||
if (shiny == Shiny.AlwaysStar && type != Shiny.AlwaysStar)
|
||||
return false;
|
||||
}
|
||||
if (shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(GetShinyXor(pid, pk.ID32), 16))
|
||||
return false;
|
||||
pk.PID = pid;
|
||||
|
||||
// Check IVs: Create flawless IVs at random indexes, then the random IVs for not flawless.
|
||||
|
|
|
|||
|
|
@ -62,10 +62,10 @@ public static bool GenerateData(PK9 pk, in GenerateParam9 enc, in EncounterCrite
|
|||
{
|
||||
var rand = new Xoroshiro128Plus(seed);
|
||||
pk.EncryptionConstant = (uint)rand.NextInt(uint.MaxValue);
|
||||
pk.PID = GetAdaptedPID(ref rand, pk, enc);
|
||||
|
||||
if (enc.Shiny is Shiny.Random && criteria.Shiny.IsShiny() != pk.IsShiny)
|
||||
var pid = GetAdaptedPID(ref rand, pk, enc);
|
||||
if (enc.Shiny is Shiny.Random && criteria.IsSpecifiedShiny() && !criteria.IsSatisfiedShiny(GetShinyXor(pid, pk.ID32), 16))
|
||||
return false;
|
||||
pk.PID = pid;
|
||||
|
||||
const int UNSET = -1;
|
||||
const int MAX = 31;
|
||||
|
|
@ -121,7 +121,7 @@ public static bool GenerateData(PK9 pk, in GenerateParam9 enc, in EncounterCrite
|
|||
return false;
|
||||
pk.Gender = gender;
|
||||
|
||||
var nature = enc.Nature != Nature.Random ? enc.Nature : enc.Species == (int)Species.Toxtricity
|
||||
var nature = enc.Nature.IsFixed ? enc.Nature : enc.Species == (int)Species.Toxtricity
|
||||
? ToxtricityUtil.GetRandomNature(ref rand, pk.Form)
|
||||
: (Nature)rand.NextInt(25);
|
||||
|
||||
|
|
@ -205,7 +205,7 @@ public static bool IsMatch(PKM pk, in GenerateParam9 enc, in ulong seed)
|
|||
if (pk.Gender != gender)
|
||||
return false;
|
||||
|
||||
var nature = enc.Nature != Nature.Random ? enc.Nature : enc.Species == (int)Species.Toxtricity
|
||||
var nature = enc.Nature.IsFixed ? enc.Nature : enc.Species == (int)Species.Toxtricity
|
||||
? ToxtricityUtil.GetRandomNature(ref rand, pk.Form)
|
||||
: (Nature)rand.NextInt(25);
|
||||
if (pk.Nature != nature)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ namespace PKHeX.Core;
|
|||
/// Parameters used to generate data for an encounter.
|
||||
/// </summary>
|
||||
/// <param name="Species">Species to generate.</param>
|
||||
/// <param name="GenderRatio">Gender ratio byte.</param>
|
||||
/// <param name="GenderRatio">Gender ratio byte from Personal Info.</param>
|
||||
/// <param name="FlawlessIVs">Count of IVs that are perfect.</param>
|
||||
/// <param name="RollCount">Count of shiny rolls allowed for the PID calculation.</param>
|
||||
/// <param name="Height">Height value to generate. If zero, full random.</param>
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ public static bool IsHeldItemAllowed(int item, EntityContext context)
|
|||
|
||||
// Combined bitflags for released held items across generations.
|
||||
private static readonly bool[] ReleasedHeldItems_2 = GetPermitList(MaxItemID_2, HeldItems_GSC);
|
||||
private static readonly bool[] ReleasedHeldItems_3 = GetPermitList(MaxItemID_3, HeldItems_RS, ItemStorage3RS.Unreleased); // Safari Ball
|
||||
private static readonly bool[] ReleasedHeldItems_3 = GetPermitList(MaxItemID_3_RS, HeldItems_RS, ItemStorage3RS.Unreleased); // Safari Ball
|
||||
private static readonly bool[] ReleasedHeldItems_4 = GetPermitList(MaxItemID_4_HGSS, HeldItems_HGSS, ItemStorage4.Unreleased);
|
||||
private static readonly bool[] ReleasedHeldItems_5 = GetPermitList(MaxItemID_5_B2W2, HeldItems_BW, ItemStorage5.Unreleased);
|
||||
private static readonly bool[] ReleasedHeldItems_6 = GetPermitList(MaxItemID_6_AO, HeldItems_AO, ItemStorage6XY.Unreleased);
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ private void VerifyTrash(LegalityAnalysis data, G3PKM pk)
|
|||
VerifyTrashCXD(data, pk);
|
||||
}
|
||||
|
||||
private void VerifyTrashCXD(LegalityAnalysis data, G3PKM pk)
|
||||
private static void VerifyTrashCXD(LegalityAnalysis data, G3PKM pk)
|
||||
{
|
||||
// Buffers should be entirely clean.
|
||||
var ot = pk.OriginalTrainerTrash;
|
||||
|
|
@ -122,7 +122,10 @@ private static void VerifyTrashINT(LegalityAnalysis data, PK3 pk)
|
|||
var trash = pk.OriginalTrainerTrash;
|
||||
// OT name from save file is copied byte-for-byte. All 8 bytes are initialized to FF on new game.
|
||||
if (!TrashByteRules3.IsTerminatedFFZero(trash, 7))
|
||||
data.AddLine(GetInvalid(Trainer, TrashBytesMissingTerminatorFinal));
|
||||
{
|
||||
if (!TrashByteRules3.IsTrashPatternDefaultTrainer(trash, pk.Version, (LanguageID)pk.Language))
|
||||
data.AddLine(GetInvalid(Trainer, TrashBytesMissingTerminatorFinal));
|
||||
}
|
||||
// Nickname can be all FF's (nicknamed) or whatever random garbage is in the buffer before filling. Unsure if we can reliably check this, but it should be "dirty" usually.
|
||||
// If it is clean, flag as fishy.
|
||||
FlagIsNicknameClean(data, pk);
|
||||
|
|
@ -132,7 +135,9 @@ private static void FlagIsNicknameClean(LegalityAnalysis data, PK3 pk)
|
|||
{
|
||||
if (!pk.IsNicknamed || pk.IsEgg)
|
||||
return;
|
||||
var nick = pk.NicknameTrash;
|
||||
// Japanese only fills the first 5+1 bytes; everything else is trash.
|
||||
// International games are 10 chars (full buffer) max; implicit terminator if full.
|
||||
var nick = pk.GetNicknamePrefillRegion();
|
||||
if (!TrashByteRules3.IsTerminatedFF(nick))
|
||||
data.AddLine(GetInvalid(Trainer, TrashBytesMismatchInitial));
|
||||
}
|
||||
|
|
@ -145,6 +150,10 @@ public static class TrashByteRules3
|
|||
// When transferred to Colosseum/XD, the encoding method switches to u16[length], thus discarding the original buffer along with its "trash".
|
||||
// For original encounters from a mainline save file,
|
||||
// - OT Name: the game copies the entire buffer from the save file OT as the PK3's OT. Thus, that must match exactly.
|
||||
// - - Japanese OT names are 5 chars, international is 7 chars. Manually entered strings are FF terminated to max length + 1.
|
||||
// - - Default OT (Japanese) names were padded with FF to len=6, so they always match manually entered names (no trash).
|
||||
// - - Default OT (International) names from the character select screen can have trash bytes due to being un-padded (single FF end of string, saves ROM space).
|
||||
// - - verification of Default OTs todo (if OT dirty, check if is default with expected trash pattern)
|
||||
// - Nickname: the buffer has garbage RAM data leftover in the nickname field, thus it should be "dirty" usually.
|
||||
// - Nicknamed: when nicknamed, the game fills the buffer with FFs then applies the nickname.
|
||||
// For event encounters from GameCube:
|
||||
|
|
@ -173,7 +182,7 @@ public static bool IsResetTrash(PK3 pk3)
|
|||
public static bool IsTerminatedZero(ReadOnlySpan<byte> data)
|
||||
{
|
||||
var first = TrashBytes8.GetTerminatorIndex(data);
|
||||
if (first == -1 || first >= data.Length - 2)
|
||||
if (first == -1 || first >= data.Length - 1)
|
||||
return true;
|
||||
return !data[(first+1)..].ContainsAnyExcept<byte>(0);
|
||||
}
|
||||
|
|
@ -181,7 +190,7 @@ public static bool IsTerminatedZero(ReadOnlySpan<byte> data)
|
|||
public static bool IsTerminatedFF(ReadOnlySpan<byte> data)
|
||||
{
|
||||
var first = TrashBytes8.GetTerminatorIndex(data);
|
||||
if (first == -1 || first >= data.Length - 2)
|
||||
if (first == -1 || first >= data.Length - 1)
|
||||
return true;
|
||||
return !data[(first + 1)..].ContainsAnyExcept<byte>(0xFF);
|
||||
}
|
||||
|
|
@ -192,17 +201,77 @@ public static bool IsTerminatedFFZero(ReadOnlySpan<byte> data, int preFill = 0)
|
|||
return IsTerminatedZero(data);
|
||||
|
||||
var first = TrashBytes8.GetTerminatorIndex(data);
|
||||
if (first == -1 || first == data.Length - 2)
|
||||
if (first == -1 || first >= data.Length - 1)
|
||||
return true;
|
||||
|
||||
first++;
|
||||
if (first < preFill)
|
||||
{
|
||||
var inner = data[first..preFill];
|
||||
if (inner.ContainsAnyExcept(Terminator))
|
||||
return false;
|
||||
first = preFill;
|
||||
if (first >= data.Length - 2)
|
||||
first++;
|
||||
if (first >= data.Length - 1)
|
||||
return true;
|
||||
}
|
||||
return !data[(first + 1)..].ContainsAnyExcept<byte>(0);
|
||||
return !data[first..].ContainsAnyExcept<byte>(0);
|
||||
}
|
||||
|
||||
// TRASH BYTES: New Game Default OTs
|
||||
// Default OT names in International (not JPN) Gen3 mainline games memcpy 7 chars and FF from the "default OT name" table, regardless of strlen.
|
||||
// Copied strings therefore contain trash from the next string entry that is encoded into the rom's string table.
|
||||
// Below is a list of possible (version, language, trash) default OTs, as initialized by the game. An `*` is used to denote the terminator.
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the specified trash byte pattern matches a default trainer name pattern for the given game <see cref="version"/> and <see cref="language"/>.
|
||||
/// </summary>
|
||||
/// <remarks>Default trainer names in certain Generation 3 Pokémon games may include trailing bytes ("trash") due to how names are stored in the game's ROM.
|
||||
/// This method checks if the provided pattern matches any of these known default patterns for the specified version and language.
|
||||
/// </remarks>
|
||||
public static bool IsTrashPatternDefaultTrainer(ReadOnlySpan<byte> trash, GameVersion version, LanguageID language) => version switch
|
||||
{
|
||||
GameVersion.R or GameVersion.S => IsTrashPatternDefaultTrainerRS(trash, language),
|
||||
GameVersion.E => IsTrashPatternDefaultTrainerE(trash, language),
|
||||
GameVersion.FR => IsTrashPatternDefaultTrainerFR(trash, language),
|
||||
GameVersion.LG => IsTrashPatternDefaultTrainerLG(trash, language),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
/// <inheritdoc cref="IsTrashPatternDefaultTrainer"/>
|
||||
/// <remarks>Default OT names present in <see cref="GameVersion.R"/> and <see cref="GameVersion.S"/> based on the language of the game.</remarks>
|
||||
public static bool IsTrashPatternDefaultTrainerRS(ReadOnlySpan<byte> trash, LanguageID language) => language switch
|
||||
{
|
||||
// TODO
|
||||
LanguageID.English => trash switch
|
||||
{
|
||||
[0xCD, 0xBB, 0xCC, 0xBB, 0xFF, 0xCE, 0xDC] => true, // SARA*Th
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
};
|
||||
|
||||
/// <inheritdoc cref="IsTrashPatternDefaultTrainer"/>
|
||||
/// <remarks>Default OT names present in <see cref="GameVersion.E"/> based on the language of the game.</remarks>
|
||||
public static bool IsTrashPatternDefaultTrainerE(ReadOnlySpan<byte> trash, LanguageID language) => language switch
|
||||
{
|
||||
// TODO
|
||||
_ => false,
|
||||
};
|
||||
|
||||
/// <inheritdoc cref="IsTrashPatternDefaultTrainer"/>
|
||||
/// <remarks>Default OT names present in <see cref="GameVersion.FR"/> based on the language of the game.</remarks>
|
||||
public static bool IsTrashPatternDefaultTrainerFR(ReadOnlySpan<byte> trash, LanguageID language) => language switch
|
||||
{
|
||||
// TODO
|
||||
_ => false,
|
||||
};
|
||||
|
||||
/// <inheritdoc cref="IsTrashPatternDefaultTrainer"/>
|
||||
/// <remarks>Default OT names present in <see cref="GameVersion.LG"/> based on the language of the game.</remarks>
|
||||
public static bool IsTrashPatternDefaultTrainerLG(ReadOnlySpan<byte> trash, LanguageID language) => language switch
|
||||
{
|
||||
// TODO
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ internal static void VerifyStatNature(LegalityAnalysis data, PKM pk)
|
|||
return;
|
||||
|
||||
// Must be a valid mint nature.
|
||||
if (!statNature.IsMint())
|
||||
if (!statNature.IsMint)
|
||||
data.AddLine(Get(Invalid, Misc, StatNatureInvalid));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ public override void Verify(LegalityAnalysis data)
|
|||
|
||||
if (pk.PID == 0)
|
||||
data.AddLine(Get(Severity.Fishy, PIDZero));
|
||||
if (!pk.Nature.IsFixed()) // out of range
|
||||
if (!pk.Nature.IsFixed) // out of range
|
||||
data.AddLine(GetInvalid(PIDNatureMismatch));
|
||||
if (data.Info.EncounterMatch is IEncounterEgg egg)
|
||||
VerifyEggPID(data, pk, egg);
|
||||
|
|
|
|||
|
|
@ -626,7 +626,7 @@ public override bool IsMatchExact(PKM pk, EvoCriteria evo)
|
|||
if (MetLevel != 0 && MetLevel != pk.MetLevel) return false;
|
||||
if ((Ball == 0 ? 4 : Ball) != pk.Ball) return false;
|
||||
if (OTGender < 2 && OTGender != pk.OriginalTrainerGender) return false;
|
||||
if (Nature != Nature.Random && pk.Nature != Nature) return false;
|
||||
if (Nature.IsFixed && pk.Nature != Nature) return false;
|
||||
if (Gender != 3 && Gender != pk.Gender) return false;
|
||||
|
||||
if (pk is IScaledSize s)
|
||||
|
|
|
|||
|
|
@ -398,4 +398,15 @@ public override int GetStringTerminatorIndex(ReadOnlySpan<byte> data)
|
|||
public override int GetStringLength(ReadOnlySpan<byte> data)
|
||||
=> TrashBytes8.GetStringLength(data);
|
||||
public override int GetBytesPerChar() => 1;
|
||||
|
||||
public override void PrepareNickname() => GetNicknamePrefillRegion().Fill(StringConverter3.TerminatorByte);
|
||||
|
||||
public Span<byte> GetNicknamePrefillRegion()
|
||||
{
|
||||
// Japanese only fills the first 5+1 bytes; everything else is trash.
|
||||
// International games are 10 chars (full buffer) max; implicit terminator if full.
|
||||
if (Japanese)
|
||||
return NicknameTrash[..6];
|
||||
return NicknameTrash;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace PKHeX.Core;
|
|||
/// Object representing a <see cref="PKM"/>'s data and derived properties.
|
||||
/// </summary>
|
||||
[DynamicallyAccessedMembers(PublicProperties | NonPublicProperties | PublicParameterlessConstructor)]
|
||||
public abstract class PKM : ISpeciesForm, ITrainerID32, IGeneration, IShiny, ILangNick, IGameValueLimit, INature, IFatefulEncounter, IStringConverter, ITrashIntrospection
|
||||
public abstract class PKM : ISpeciesForm, ITrainerID32, IGeneration, IShiny, ILangNick, IGameValueLimit, INature, IFatefulEncounter, IStringConverter, ITrashIntrospection, IContext
|
||||
{
|
||||
public abstract int SIZE_PARTY { get; }
|
||||
public abstract int SIZE_STORED { get; }
|
||||
|
|
@ -44,6 +44,11 @@ public abstract class PKM : ISpeciesForm, ITrainerID32, IGeneration, IShiny, ILa
|
|||
public abstract Span<byte> OriginalTrainerTrash { get; }
|
||||
public virtual Span<byte> HandlingTrainerTrash => [];
|
||||
|
||||
/// <summary>
|
||||
/// Conditions the <see cref="NicknameTrash"/> data to safely terminate the Nickname string from the text entry screen.
|
||||
/// </summary>
|
||||
public virtual void PrepareNickname() { }
|
||||
|
||||
protected abstract byte[] Encrypt();
|
||||
public abstract EntityContext Context { get; }
|
||||
public byte Format => Context.Generation;
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ private bool SearchSimple(PKM pk)
|
|||
return false;
|
||||
if (Ability > -1 && pk.Ability != Ability)
|
||||
return false;
|
||||
if (Nature.IsFixed() && pk.StatNature != Nature)
|
||||
if (Nature.IsFixed && pk.StatNature != Nature)
|
||||
return false;
|
||||
if (Item > -1 && pk.HeldItem != Item)
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ public abstract class G3PKM : PKM, IRibbonSetEvent3, IRibbonSetCommon3, IRibbonS
|
|||
public sealed override ushort MaxMoveID => Legal.MaxMoveID_3;
|
||||
public sealed override ushort MaxSpeciesID => Legal.MaxSpeciesID_3;
|
||||
public sealed override int MaxAbilityID => Legal.MaxAbilityID_3;
|
||||
public sealed override int MaxItemID => Legal.MaxItemID_3;
|
||||
public sealed override int MaxItemID => Legal.MaxItemID_3_E;
|
||||
public sealed override int MaxBallID => Legal.MaxBallID_3;
|
||||
public sealed override GameVersion MaxGameID => Legal.MaxGameID_3;
|
||||
public sealed override int MaxIV => 31;
|
||||
|
|
|
|||
|
|
@ -289,7 +289,7 @@ public sealed override byte Ball
|
|||
BallDPPt = Clamp(value, Core.Ball.Cherish);
|
||||
|
||||
// Only set the HG/SS value if it originated in HG/SS and was not an event.
|
||||
if (WasCreatedInHGSS)
|
||||
if (WasCreatedInHGSS && this is not BK4)
|
||||
BallHGSS = Clamp(value, Core.Ball.Sport);
|
||||
else
|
||||
BallHGSS = 0;
|
||||
|
|
|
|||
|
|
@ -35,3 +35,20 @@ public interface IStringConverter
|
|||
/// <returns>Count of bytes written to <see cref="data"/>.</returns>
|
||||
int SetString(Span<byte> data, ReadOnlySpan<char> text, int length, StringConverterOption option);
|
||||
}
|
||||
|
||||
public delegate string StringGetter(ReadOnlySpan<byte> data);
|
||||
public delegate int StringLoader(ReadOnlySpan<byte> data, Span<char> text);
|
||||
public delegate int StringSetter(Span<byte> data, ReadOnlySpan<char> text, int length, StringConverterOption option);
|
||||
|
||||
public sealed class CustomStringConverter : IStringConverter, IGeneration, IContext
|
||||
{
|
||||
public required StringGetter Get { get; init; }
|
||||
public required StringLoader Load { get; init; }
|
||||
public required StringSetter Set { get; init; }
|
||||
public required byte Generation { get; init; }
|
||||
public required EntityContext Context { get; init; }
|
||||
|
||||
public string GetString(ReadOnlySpan<byte> data) => Get(data);
|
||||
public int LoadString(ReadOnlySpan<byte> data, Span<char> text) => Load(data, text);
|
||||
public int SetString(Span<byte> data, ReadOnlySpan<char> text, int length, StringConverterOption option) => Set(data, text, length, option);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,8 +73,8 @@ public static byte GetItemOld2(ushort item)
|
|||
NaN, 058, 059, 061, 444, NaN, NaN, 216, 445, 446, // 5
|
||||
NaN, 447, 051, 038, 039, 040, 478, 464, 456, 484, // 6
|
||||
NaN, 482, 033, 217, 151, NaN, 237, 244, 149, 153, // 7
|
||||
152, 245, 221, 156, 150, 485, 086, 087, 222, 487, // 8
|
||||
NaN, 223, 486, 488, 224, 243, 248, 490, 241, 491, // 9
|
||||
152, 245, 221, 156, 150, 485, 086, 087, 222, 486, // 8
|
||||
NaN, 223, 487, 488, 224, 243, 248, 490, 241, 491, // 9
|
||||
NaN, 489, 240, 473, NaN, 259, 228, 246, 242, 157, // 10
|
||||
088, 089, 229, 247, 504, NaN, NaN, 239, 258, 230, // 11
|
||||
NaN, 034, 035, 036, 037, 238, 231, 475, 481, NaN, // 12
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -14,7 +14,7 @@
|
|||
0190 e 钢铁能检查GS球
|
||||
0191 e 钢铁能归还GS球 (与上述旗标绑定)
|
||||
0192 e GS球能放入桐树林祠堂
|
||||
1809 * No Mystery Gift item is waiting in the Pokémon Center
|
||||
1809 * 宝可梦中心没有神秘礼物等待领取
|
||||
0672 * 已解决 阿露福遗迹凤王拼图谜题
|
||||
0673 * 已解决 阿露福遗迹化石盔拼图谜题
|
||||
0674 * 已解决 阿露福遗迹菊石兽拼图谜题
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -5,7 +5,7 @@ namespace PKHeX.Core;
|
|||
/// <summary>
|
||||
/// Minimal Trainer Information necessary for generating a <see cref="PKM"/>.
|
||||
/// </summary>
|
||||
public interface ITrainerInfo : ITrainerID32ReadOnly, IVersion, IGeneration
|
||||
public interface ITrainerInfo : ITrainerID32ReadOnly, IVersion, IGeneration, IContext
|
||||
{
|
||||
string OT { get; }
|
||||
byte Gender { get; }
|
||||
|
|
@ -13,7 +13,6 @@ public interface ITrainerInfo : ITrainerID32ReadOnly, IVersion, IGeneration
|
|||
int Language { get; }
|
||||
|
||||
new byte Generation { get; }
|
||||
EntityContext Context { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
43
PKHeX.Core/Saves/Blocks/Gen3/ISaveBlock3Large.cs
Normal file
43
PKHeX.Core/Saves/Blocks/Gen3/ISaveBlock3Large.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||||
public interface ISaveBlock3Large
|
||||
{
|
||||
Memory<byte> Raw { get; }
|
||||
Span<byte> Data { get; }
|
||||
|
||||
ushort X { get; set; }
|
||||
ushort Y { get; set; }
|
||||
byte PartyCount { get; set; }
|
||||
Span<byte> PartyBuffer { get; }
|
||||
uint Money { get; set; }
|
||||
ushort Coin { get; set; }
|
||||
ushort RegisteredItem { get; set; }
|
||||
Span<byte> EReaderBerry { get; }
|
||||
Gen3MysteryData MysteryData { get; set; }
|
||||
int DaycareOffset { get; }
|
||||
int DaycareSlotSize { get; }
|
||||
int BadgeFlagStart { get; }
|
||||
int EventFlagCount { get; }
|
||||
int EventWorkCount { get; }
|
||||
int EggEventFlag { get; }
|
||||
Memory<byte> RoamerData { get; }
|
||||
uint GetRecord(int record);
|
||||
void SetRecord(int record, uint value);
|
||||
|
||||
Mail3 GetMail(int mailIndex);
|
||||
void SetMail(int mailIndex, Mail3 value);
|
||||
|
||||
bool GetEventFlag(int flagNumber);
|
||||
void SetEventFlag(int flagNumber, bool value);
|
||||
ushort GetWork(int index);
|
||||
void SetWork(int index, ushort value);
|
||||
|
||||
int SeenOffset2 { get; }
|
||||
int ExternalEventData { get; }
|
||||
int SeenOffset3 { get; }
|
||||
Span<byte> GiftRibbons { get; }
|
||||
}
|
||||
13
PKHeX.Core/Saves/Blocks/Gen3/ISaveBlock3LargeExpansion.cs
Normal file
13
PKHeX.Core/Saves/Blocks/Gen3/ISaveBlock3LargeExpansion.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public interface ISaveBlock3LargeExpansion : ISaveBlock3Large
|
||||
{
|
||||
WonderNews3 GetWonderNews(bool isJapanese);
|
||||
void SetWonderNews(bool isJapanese, ReadOnlySpan<byte> data);
|
||||
WonderCard3 GetWonderCard(bool isJapanese);
|
||||
void SetWonderCard(bool isJapanese, ReadOnlySpan<byte> data);
|
||||
WonderCard3Extra GetWonderCardExtra(bool isJapanese);
|
||||
void SetWonderCardExtra(bool isJapanese, ReadOnlySpan<byte> data);
|
||||
}
|
||||
|
|
@ -3,13 +3,13 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Properties common to RS & Emerald save files.
|
||||
/// Large-block properties common to RS & Emerald save files.
|
||||
/// </summary>
|
||||
public interface IGen3Hoenn
|
||||
public interface ISaveBlock3LargeHoenn : ISaveBlock3Large
|
||||
{
|
||||
RTC3 ClockInitial { get; set; }
|
||||
RTC3 ClockElapsed { get; set; }
|
||||
PokeBlock3Case PokeBlocks { get; set; }
|
||||
ushort GetBerryBlenderRPMRecord(int index);
|
||||
void SetBerryBlenderRPMRecord(int index, ushort value);
|
||||
DecorationInventory3 Decorations { get; }
|
||||
Swarm3 Swarm { get; set; }
|
||||
|
||||
|
|
@ -19,6 +19,6 @@ public interface IGen3Hoenn
|
|||
RecordMixing3Gift RecordMixingGift { get; set; }
|
||||
SecretBaseManager3 SecretBases { get; }
|
||||
|
||||
Paintings3 GetPainting(int index);
|
||||
Paintings3 GetPainting(int index, bool japanese);
|
||||
void SetPainting(int index, Paintings3 value);
|
||||
}
|
||||
36
PKHeX.Core/Saves/Blocks/Gen3/ISaveBlock3Small.cs
Normal file
36
PKHeX.Core/Saves/Blocks/Gen3/ISaveBlock3Small.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||||
public interface ISaveBlock3Small
|
||||
{
|
||||
Memory<byte> Raw { get; }
|
||||
Span<byte> Data { get; }
|
||||
|
||||
Span<byte> OriginalTrainerTrash { get; }
|
||||
byte Gender { get; set; }
|
||||
uint ID32 { get; set; }
|
||||
ushort TID16 { get; set; }
|
||||
ushort SID16 { get; set; }
|
||||
int PlayedHours { get; set; }
|
||||
int PlayedMinutes { get; set; }
|
||||
int PlayedSeconds { get; set; }
|
||||
byte PlayedFrames { get; set; }
|
||||
byte OptionsButtonMode { get; set; }
|
||||
int TextSpeed { get; set; }
|
||||
byte OptionWindowFrame { get; set; }
|
||||
bool OptionSoundStereo { get; set; }
|
||||
bool OptionBattleStyle { get; set; }
|
||||
bool OptionBattleScene { get; set; }
|
||||
bool OptionIsRegionMapZoom { get; set; }
|
||||
byte PokedexSort { get; set; }
|
||||
byte PokedexMode { get; set; }
|
||||
byte PokedexNationalMagicRSE { get; set; }
|
||||
byte PokedexNationalMagicFRLG { get; set; }
|
||||
uint DexPIDUnown { get; set; }
|
||||
uint DexPIDSpinda { get; set; }
|
||||
Span<byte> EReaderTrainer { get; }
|
||||
uint SecurityKey { get; }
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public interface IGen3Joyful
|
||||
public interface ISaveBlock3SmallExpansion : ISaveBlock3Small
|
||||
{
|
||||
ushort JoyfulJumpInRow { get; set; }
|
||||
ushort JoyfulJump5InRow { get; set; }
|
||||
|
|
@ -11,6 +13,9 @@ public interface IGen3Joyful
|
|||
ushort JoyfulBerriesInRow { get; set; }
|
||||
ushort JoyfulBerries5InRow { get; set; }
|
||||
|
||||
ushort GetBerryPressSpeed([Range(0, 3)] int index);
|
||||
void SetBerryPressSpeed([Range(0, 3)] int index, ushort value);
|
||||
uint BerryPowder { get; set; }
|
||||
uint SecurityKey { get; set; }
|
||||
new uint SecurityKey { get; set; }
|
||||
uint LinkFlags { get; set; }
|
||||
}
|
||||
17
PKHeX.Core/Saves/Blocks/Gen3/ISaveBlock3SmallHoenn.cs
Normal file
17
PKHeX.Core/Saves/Blocks/Gen3/ISaveBlock3SmallHoenn.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Small-block properties common to RS & Emerald save files.
|
||||
/// </summary>
|
||||
public interface ISaveBlock3SmallHoenn : ISaveBlock3Small
|
||||
{
|
||||
/// <summary>
|
||||
/// localTimeOffset
|
||||
/// </summary>
|
||||
RTC3 ClockInitial { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// lastBerryTreeUpdate
|
||||
/// </summary>
|
||||
RTC3 ClockElapsed { get; set; }
|
||||
}
|
||||
204
PKHeX.Core/Saves/Blocks/Gen3/SaveBlock3LargeE.cs
Normal file
204
PKHeX.Core/Saves/Blocks/Gen3/SaveBlock3LargeE.cs
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed record SaveBlock3LargeE(Memory<byte> Raw) : ISaveBlock3LargeExpansion, ISaveBlock3LargeHoenn, IRecordStatStorage<RecID3Emerald, uint>
|
||||
{
|
||||
public Span<byte> Data => Raw.Span;
|
||||
public ushort X { get => ReadUInt16LittleEndian(Data); set => WriteUInt16LittleEndian(Data, value); }
|
||||
public ushort Y { get => ReadUInt16LittleEndian(Data[2..]); set => WriteUInt16LittleEndian(Data[2..], value); }
|
||||
|
||||
public byte PartyCount { get => Data[0x234]; set => Data[0x234] = value; }
|
||||
public Span<byte> PartyBuffer => Data.Slice(0x238, 6 * PokeCrypto.SIZE_3PARTY);
|
||||
|
||||
public uint Money { get => ReadUInt32LittleEndian(Data[0x0490..]); set => WriteUInt32LittleEndian(Data[0x0490..], value); }
|
||||
public ushort Coin { get => ReadUInt16LittleEndian(Data[0x0494..]); set => WriteUInt16LittleEndian(Data[0x0494..], value); }
|
||||
public ushort RegisteredItem { get => ReadUInt16LittleEndian(Data[0x0496..]); set => WriteUInt16LittleEndian(Data[0x0496..], value); }
|
||||
public Span<byte> Inventory => Data.Slice(0x0498, 0x3B0);
|
||||
private Span<byte> PokeBlockData => Data.Slice(0x848, PokeBlock3Case.SIZE);
|
||||
public PokeBlock3Case PokeBlocks { get => new(PokeBlockData); set => value.Write(PokeBlockData); }
|
||||
public int SeenOffset2 => 0x988;
|
||||
|
||||
private const int OFS_BerryBlenderRecord = 0x9BC;
|
||||
|
||||
/// <summary>
|
||||
/// Max RPM for 2, 3 and 4 players. Each value unit represents 0.01 RPM. Value 0 if no record.
|
||||
/// </summary>
|
||||
/// <remarks>2 players: index 0, 3 players: index 1, 4 players: index 2</remarks>
|
||||
public const int BerryBlenderRPMRecordCount = 3;
|
||||
|
||||
private Span<byte> GetBlenderRPMSpan(int index)
|
||||
{
|
||||
if ((uint)index >= BerryBlenderRPMRecordCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
return Data[(OFS_BerryBlenderRecord + (index * 2))..];
|
||||
}
|
||||
|
||||
public ushort GetBerryBlenderRPMRecord(int index) => ReadUInt16LittleEndian(GetBlenderRPMSpan(index));
|
||||
public void SetBerryBlenderRPMRecord(int index, ushort value) => WriteUInt16LittleEndian(GetBlenderRPMSpan(index), value);
|
||||
|
||||
private const int EventFlag = 0x1270;
|
||||
private const int EventWork = 0x139C;
|
||||
public int EventFlagCount => 8 * 300;
|
||||
public int EventWorkCount => 0x100;
|
||||
public int EggEventFlag => 0x86;
|
||||
public int BadgeFlagStart => 0x867;
|
||||
|
||||
public bool GetEventFlag(int flagNumber)
|
||||
{
|
||||
if ((uint)flagNumber >= EventFlagCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to get ({flagNumber}) is greater than max ({EventFlagCount}).");
|
||||
return FlagUtil.GetFlag(Data, EventFlag + (flagNumber >> 3), flagNumber & 7);
|
||||
}
|
||||
|
||||
public void SetEventFlag(int flagNumber, bool value)
|
||||
{
|
||||
if ((uint)flagNumber >= EventFlagCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to set ({flagNumber}) is greater than max ({EventFlagCount}).");
|
||||
FlagUtil.SetFlag(Data, EventFlag + (flagNumber >> 3), flagNumber & 7, value);
|
||||
}
|
||||
|
||||
public ushort GetWork(int index) => ReadUInt16LittleEndian(Data[(EventWork + (index * 2))..]);
|
||||
public void SetWork(int index, ushort value) => WriteUInt16LittleEndian(Data[EventWork..][(index * 2)..], value);
|
||||
|
||||
private const int RecordOffset = 0x159C;
|
||||
private static int GetRecordOffset(RecID3Emerald record)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)record, (uint)RecID3Emerald.NUM_GAME_STATS);
|
||||
return RecordOffset + (sizeof(uint) * (int)record);
|
||||
}
|
||||
|
||||
public uint GetRecord(int record) => GetRecord((RecID3Emerald)record);
|
||||
public void SetRecord(int record, uint value) => SetRecord((RecID3Emerald)record, value);
|
||||
public uint GetRecord(RecID3Emerald record) => ReadUInt32LittleEndian(Data[GetRecordOffset(record)..]);
|
||||
public void SetRecord(RecID3Emerald record, uint value) => WriteUInt32LittleEndian(Data[GetRecordOffset(record)..], value);
|
||||
public void AddRecord(RecID3Emerald record, uint value) => SetRecord(record, GetRecord(record) + value);
|
||||
|
||||
private Memory<byte> SecretBaseData => Raw.Slice(0x1A9C, SecretBaseManager3.BaseCount * SecretBase3.SIZE);
|
||||
public SecretBaseManager3 SecretBases => new(SecretBaseData);
|
||||
|
||||
public DecorationInventory3 Decorations => new(Data.Slice(0x2734, DecorationInventory3.SIZE));
|
||||
|
||||
private Span<byte> SwarmData => Data.Slice(0x2B90, Swarm3.SIZE);
|
||||
public Swarm3 Swarm { get => new(SwarmData.ToArray()); set => value.Data.CopyTo(SwarmData); }
|
||||
private void ClearSwarm() => SwarmData.Clear();
|
||||
public IReadOnlyList<Swarm3> DefaultSwarms => Swarm3Details.Swarms_E;
|
||||
|
||||
public int SwarmIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
var map = Swarm.MapNum;
|
||||
for (int i = 0; i < DefaultSwarms.Count; i++)
|
||||
{
|
||||
if (DefaultSwarms[i].MapNum == map)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
set
|
||||
{
|
||||
var arr = DefaultSwarms;
|
||||
if ((uint)value >= arr.Count)
|
||||
ClearSwarm();
|
||||
else
|
||||
Swarm = arr[value];
|
||||
}
|
||||
}
|
||||
|
||||
private const int MailOffset = 0x2BE0;
|
||||
private static int GetMailOffset(int index) => (index * Mail3.SIZE) + MailOffset;
|
||||
private Span<byte> GetMailSpan(int ofs) => Data.Slice(ofs, Mail3.SIZE);
|
||||
|
||||
public Mail3 GetMail(int mailIndex)
|
||||
{
|
||||
var ofs = GetMailOffset(mailIndex);
|
||||
var span = Data.Slice(ofs, Mail3.SIZE);
|
||||
return new Mail3(span.ToArray(), ofs);
|
||||
}
|
||||
|
||||
public void SetMail(int mailIndex, Mail3 value)
|
||||
{
|
||||
var ofs = GetMailOffset(mailIndex);
|
||||
value.CopyTo(GetMailSpan(ofs));
|
||||
}
|
||||
|
||||
private const int OFS_TrendyWord = 0x2E20;
|
||||
public bool GetTrendyWordUnlocked(TrendyWord3E word) => FlagUtil.GetFlag(Data, OFS_TrendyWord + ((byte)word >> 3), (byte)word & 7);
|
||||
public void SetTrendyWordUnlocked(TrendyWord3E word, bool value) => FlagUtil.SetFlag(Data, OFS_TrendyWord + ((byte)word >> 3), (byte)word & 7, value);
|
||||
|
||||
private const int Painting = 0x2F90;
|
||||
private const int PaintingCount = 5;
|
||||
private Span<byte> GetPaintingSpan(int index)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual<uint>((uint)index, PaintingCount);
|
||||
return Data.Slice(Painting + (Paintings3.SIZE * index), Paintings3.SIZE * PaintingCount);
|
||||
}
|
||||
|
||||
public Paintings3 GetPainting(int index, bool japanese) => new(GetPaintingSpan(index).ToArray(), japanese);
|
||||
public void SetPainting(int index, Paintings3 value) => value.Data.CopyTo(GetPaintingSpan(index));
|
||||
|
||||
public int DaycareOffset => 0x3030;
|
||||
public int DaycareSlotSize => PokeCrypto.SIZE_3STORED + 0x3C; // 0x38 mail + 4 exp
|
||||
|
||||
public uint DaycareSeed
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Data[0x3148..]);
|
||||
set => WriteUInt32LittleEndian(Data[0x3148..], value);
|
||||
}
|
||||
|
||||
public Span<byte> GiftRibbons => Data.Slice(0x31B3, 11);
|
||||
public int ExternalEventData => 0x31B3;
|
||||
public Memory<byte> RoamerData => Raw.Slice(0x31DC, Roamer3.SIZE);
|
||||
private const int OFFSET_EBERRY = 0x31F8;
|
||||
private const int SIZE_EBERRY = 0x34;
|
||||
public Span<byte> EReaderBerry => Data.Slice(OFFSET_EBERRY, SIZE_EBERRY);
|
||||
|
||||
public const int WonderNewsOffset = 0x322C;
|
||||
|
||||
// RAM Script
|
||||
private Span<byte> MysterySpan => Data.Slice(0x3728, MysteryEvent3.SIZE);
|
||||
public Gen3MysteryData MysteryData
|
||||
{
|
||||
get => new MysteryEvent3(MysterySpan.ToArray());
|
||||
set => value.Data.CopyTo(MysterySpan);
|
||||
}
|
||||
|
||||
private static int WonderCardOffset(bool isJapanese) => WonderNewsOffset + (isJapanese ? WonderNews3.SIZE_JAP : WonderNews3.SIZE);
|
||||
private static int WonderCardExtraOffset(bool isJapanese) => WonderCardOffset(isJapanese) + (isJapanese ? WonderCard3.SIZE_JAP : WonderCard3.SIZE);
|
||||
|
||||
private Span<byte> WonderNewsData(bool isJapanese) => Data.Slice(WonderNewsOffset, isJapanese ? WonderNews3.SIZE_JAP : WonderNews3.SIZE);
|
||||
private Span<byte> WonderCardData(bool isJapanese) => Data.Slice(WonderCardOffset(isJapanese), isJapanese ? WonderCard3.SIZE_JAP : WonderCard3.SIZE);
|
||||
private Span<byte> WonderCardExtraData(bool isJapanese) => Data.Slice(WonderCardExtraOffset(isJapanese), WonderCard3Extra.SIZE);
|
||||
|
||||
public WonderNews3 GetWonderNews(bool isJapanese) => new(WonderNewsData(isJapanese).ToArray());
|
||||
public void SetWonderNews(bool isJapanese, ReadOnlySpan<byte> data) => data.CopyTo(WonderNewsData(isJapanese));
|
||||
public WonderCard3 GetWonderCard(bool isJapanese) => new(WonderCardData(isJapanese).ToArray());
|
||||
public void SetWonderCard(bool isJapanese, ReadOnlySpan<byte> data) => data.CopyTo(WonderCardData(isJapanese));
|
||||
public WonderCard3Extra GetWonderCardExtra(bool isJapanese) => new(WonderCardExtraData(isJapanese).ToArray());
|
||||
public void SetWonderCardExtra(bool isJapanese, ReadOnlySpan<byte> data) => data.CopyTo(WonderCardExtraData(isJapanese));
|
||||
|
||||
private const int OFS_TrainerHillRecord = 0x3718;
|
||||
|
||||
/** Each value unit represents 1/60th of a second. Value 0 if no record. */
|
||||
public uint GetTrainerHillRecord(TrainerHillMode3E mode) => ReadUInt32LittleEndian(Data[(OFS_TrainerHillRecord + ((byte)mode * 4))..]);
|
||||
public void SetTrainerHillRecord(TrainerHillMode3E mode, uint value) => WriteUInt32LittleEndian(Data[(OFS_TrainerHillRecord + ((byte)mode * 4))..], value);
|
||||
|
||||
private Span<byte> RecordMixingData => Data.Slice(0x3B14, RecordMixing3Gift.SIZE);
|
||||
public RecordMixing3Gift RecordMixingGift
|
||||
{
|
||||
get => new(RecordMixingData.ToArray());
|
||||
set => value.Data.CopyTo(RecordMixingData);
|
||||
}
|
||||
|
||||
public int SeenOffset3 => 0x3B24;
|
||||
|
||||
private const int Walda = 0x3D70;
|
||||
public ushort WaldaBackgroundColor { get => ReadUInt16LittleEndian(Data[(Walda + 0)..]); set => WriteUInt16LittleEndian(Data[(Walda + 0)..], value); }
|
||||
public ushort WaldaForegroundColor { get => ReadUInt16LittleEndian(Data[(Walda + 2)..]); set => WriteUInt16LittleEndian(Data[(Walda + 2)..], value); }
|
||||
public byte WaldaIconID { get => Data[Walda + 0x14]; set => Data[Walda + 0x14] = value; }
|
||||
public byte WaldaPatternID { get => Data[Walda + 0x15]; set => Data[Walda + 0x15] = value; }
|
||||
public bool WaldaUnlocked { get => Data[Walda + 0x16] != 0; set => Data[Walda + 0x16] = (byte)(value ? 1 : 0); }
|
||||
}
|
||||
121
PKHeX.Core/Saves/Blocks/Gen3/SaveBlock3LargeFRLG.cs
Normal file
121
PKHeX.Core/Saves/Blocks/Gen3/SaveBlock3LargeFRLG.cs
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
using System;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed record SaveBlock3LargeFRLG(Memory<byte> Raw) : ISaveBlock3LargeExpansion, IRecordStatStorage<RecID3FRLG, uint>
|
||||
{
|
||||
public Span<byte> Data => Raw.Span;
|
||||
public ushort X { get => ReadUInt16LittleEndian(Data); set => WriteUInt16LittleEndian(Data, value); }
|
||||
public ushort Y { get => ReadUInt16LittleEndian(Data[2..]); set => WriteUInt16LittleEndian(Data[2..], value); }
|
||||
|
||||
public byte PartyCount { get => Data[0x034]; set => Data[0x034] = value; }
|
||||
public Span<byte> PartyBuffer => Data.Slice(0x038, 6 * PokeCrypto.SIZE_3PARTY);
|
||||
|
||||
public uint Money { get => ReadUInt32LittleEndian(Data[0x0290..]); set => WriteUInt32LittleEndian(Data[0x0290..], value); }
|
||||
public ushort Coin { get => ReadUInt16LittleEndian(Data[0x0294..]); set => WriteUInt16LittleEndian(Data[0x0294..], value); }
|
||||
public ushort RegisteredItem { get => ReadUInt16LittleEndian(Data[0x0296..]); set => WriteUInt16LittleEndian(Data[0x0296..], value); }
|
||||
public Span<byte> Inventory => Data.Slice(0x0298, 0x360);
|
||||
public int SeenOffset2 => 0x5F8;
|
||||
|
||||
private const int EventFlag = 0xEE0;
|
||||
private const int EventWork = 0x1000;
|
||||
|
||||
public int EventFlagCount => 8 * 288;
|
||||
public int EventWorkCount => 0x100;
|
||||
public int EggEventFlag => 0x266;
|
||||
public int BadgeFlagStart => 0x820;
|
||||
|
||||
public bool GetEventFlag(int flagNumber)
|
||||
{
|
||||
if ((uint)flagNumber >= EventFlagCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to get ({flagNumber}) is greater than max ({EventFlagCount}).");
|
||||
return FlagUtil.GetFlag(Data, EventFlag + (flagNumber >> 3), flagNumber & 7);
|
||||
}
|
||||
|
||||
public void SetEventFlag(int flagNumber, bool value)
|
||||
{
|
||||
if ((uint)flagNumber >= EventFlagCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to set ({flagNumber}) is greater than max ({EventFlagCount}).");
|
||||
FlagUtil.SetFlag(Data, EventFlag + (flagNumber >> 3), flagNumber & 7, value);
|
||||
}
|
||||
|
||||
public ushort GetWork(int index) => ReadUInt16LittleEndian(Data[(EventWork + (index * 2))..]);
|
||||
public void SetWork(int index, ushort value) => WriteUInt16LittleEndian(Data[EventWork..][(index * 2)..], value);
|
||||
|
||||
private const int RecordOffset = 0x1200;
|
||||
private static int GetRecordOffset(RecID3FRLG record)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)record, (uint)RecID3FRLG.NUM_GAME_STATS);
|
||||
return RecordOffset + (sizeof(uint) * (int)record);
|
||||
}
|
||||
|
||||
public uint GetRecord(int record) => GetRecord((RecID3FRLG)record);
|
||||
public void SetRecord(int record, uint value) => SetRecord((RecID3FRLG)record, value);
|
||||
public uint GetRecord(RecID3FRLG record) => ReadUInt32LittleEndian(Data[GetRecordOffset(record)..]);
|
||||
public void SetRecord(RecID3FRLG record, uint value) => WriteUInt32LittleEndian(Data[GetRecordOffset(record)..], value);
|
||||
public void AddRecord(RecID3FRLG record, uint value) => SetRecord(record, GetRecord(record) + value);
|
||||
|
||||
|
||||
private const int MailOffset = 0x2CD0;
|
||||
private static int GetMailOffset(int index) => (index * Mail3.SIZE) + MailOffset;
|
||||
private Span<byte> GetMailSpan(int ofs) => Data.Slice(ofs, Mail3.SIZE);
|
||||
|
||||
public Mail3 GetMail(int mailIndex)
|
||||
{
|
||||
var ofs = GetMailOffset(mailIndex);
|
||||
var span = Data.Slice(ofs, Mail3.SIZE);
|
||||
return new Mail3(span.ToArray(), ofs);
|
||||
}
|
||||
|
||||
public void SetMail(int mailIndex, Mail3 value)
|
||||
{
|
||||
var ofs = GetMailOffset(mailIndex);
|
||||
value.CopyTo(GetMailSpan(ofs));
|
||||
}
|
||||
|
||||
public int DaycareOffset => 0x2F80;
|
||||
public int DaycareSlotSize => PokeCrypto.SIZE_3STORED + 0x3C; // 0x38 mail + 4 exp
|
||||
|
||||
public ushort DaycareSeed
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Data[0x3098..]);
|
||||
set => WriteUInt16LittleEndian(Data[0x3098..], value);
|
||||
}
|
||||
|
||||
public Span<byte> GiftRibbons => Data.Slice(0x309C, 11);
|
||||
public int ExternalEventData => 0x30A7;
|
||||
public Memory<byte> RoamerData => Raw.Slice(0x30D0, Roamer3.SIZE);
|
||||
|
||||
private const int OFFSET_EBERRY = 0x30EC;
|
||||
private const int SIZE_EBERRY = 0x34;
|
||||
public Span<byte> EReaderBerry => Data.Slice(OFFSET_EBERRY, SIZE_EBERRY);
|
||||
|
||||
public const int WonderNewsOffset = 0x3120;
|
||||
|
||||
private static int WonderCardOffset(bool isJapanese) => WonderNewsOffset + (isJapanese ? WonderNews3.SIZE_JAP : WonderNews3.SIZE);
|
||||
private static int WonderCardExtraOffset(bool isJapanese) => WonderCardOffset(isJapanese) + (isJapanese ? WonderCard3.SIZE_JAP : WonderCard3.SIZE);
|
||||
|
||||
private Span<byte> WonderNewsData(bool isJapanese) => Data.Slice(WonderNewsOffset, isJapanese ? WonderNews3.SIZE_JAP : WonderNews3.SIZE);
|
||||
private Span<byte> WonderCardData(bool isJapanese) => Data.Slice(WonderCardOffset(isJapanese), isJapanese ? WonderCard3.SIZE_JAP : WonderCard3.SIZE);
|
||||
private Span<byte> WonderCardExtraData(bool isJapanese) => Data.Slice(WonderCardExtraOffset(isJapanese), WonderCard3Extra.SIZE);
|
||||
|
||||
public WonderNews3 GetWonderNews(bool isJapanese) => new(WonderNewsData(isJapanese).ToArray());
|
||||
public void SetWonderNews(bool isJapanese, ReadOnlySpan<byte> data) => data.CopyTo(WonderNewsData(isJapanese));
|
||||
public WonderCard3 GetWonderCard(bool isJapanese) => new(WonderCardData(isJapanese).ToArray());
|
||||
public void SetWonderCard(bool isJapanese, ReadOnlySpan<byte> data) => data.CopyTo(WonderCardData(isJapanese));
|
||||
public WonderCard3Extra GetWonderCardExtra(bool isJapanese) => new(WonderCardExtraData(isJapanese).ToArray());
|
||||
public void SetWonderCardExtra(bool isJapanese, ReadOnlySpan<byte> data) => data.CopyTo(WonderCardExtraData(isJapanese));
|
||||
|
||||
public Span<byte> RivalNameTrash => Data.Slice(0x3A4C, 8);
|
||||
|
||||
private Span<byte> MysterySpan => Data.Slice(0x361C, MysteryEvent3.SIZE);
|
||||
public Gen3MysteryData MysteryData
|
||||
{
|
||||
get => new MysteryEvent3(MysterySpan.ToArray());
|
||||
set => value.Data.CopyTo(MysterySpan);
|
||||
}
|
||||
|
||||
public int SeenOffset3 => 0x3A18;
|
||||
public Memory<byte> SingleDaycareRoute5 => Raw.Slice(0x3C98, PokeCrypto.SIZE_3STORED); // 0x38 mail + 4 exp
|
||||
}
|
||||
172
PKHeX.Core/Saves/Blocks/Gen3/SaveBlock3LargeRS.cs
Normal file
172
PKHeX.Core/Saves/Blocks/Gen3/SaveBlock3LargeRS.cs
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed record SaveBlock3LargeRS(Memory<byte> Raw) : ISaveBlock3LargeHoenn, IRecordStatStorage<RecID3RuSa, uint>
|
||||
{
|
||||
public Span<byte> Data => Raw.Span;
|
||||
public ushort X { get => ReadUInt16LittleEndian(Data); set => WriteUInt16LittleEndian(Data, value); }
|
||||
public ushort Y { get => ReadUInt16LittleEndian(Data[2..]); set => WriteUInt16LittleEndian(Data[2..], value); }
|
||||
|
||||
public byte PartyCount { get => Data[0x234]; set => Data[0x234] = value; }
|
||||
public Span<byte> PartyBuffer => Data.Slice(0x238, 6 * PokeCrypto.SIZE_3PARTY);
|
||||
|
||||
public uint Money { get => ReadUInt32LittleEndian(Data[0x0490..]); set => WriteUInt32LittleEndian(Data[0x0490..], value); }
|
||||
public ushort Coin { get => ReadUInt16LittleEndian(Data[0x0494..]); set => WriteUInt16LittleEndian(Data[0x0494..], value); }
|
||||
public ushort RegisteredItem { get => ReadUInt16LittleEndian(Data[0x0496..]); set => WriteUInt16LittleEndian(Data[0x0496..], value); }
|
||||
public Span<byte> Inventory => Data.Slice(0x498, 0x360);
|
||||
private Span<byte> PokeBlockData => Data.Slice(0x7F8, PokeBlock3Case.SIZE);
|
||||
public PokeBlock3Case PokeBlocks { get => new(PokeBlockData); set => value.Write(PokeBlockData); }
|
||||
public int SeenOffset2 => 0x938;
|
||||
|
||||
private const int OFS_BerryBlenderRecord = 0x96C;
|
||||
|
||||
/// <summary>
|
||||
/// Max RPM for 2, 3 and 4 players. Each value unit represents 0.01 RPM. Value 0 if no record.
|
||||
/// </summary>
|
||||
/// <remarks>2 players: index 0, 3 players: index 1, 4 players: index 2</remarks>
|
||||
public const int BerryBlenderRPMRecordCount = 3;
|
||||
|
||||
private Span<byte> GetBlenderRPMSpan(int index)
|
||||
{
|
||||
if ((uint)index >= BerryBlenderRPMRecordCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
return Data[(OFS_BerryBlenderRecord + (index * 2))..];
|
||||
}
|
||||
|
||||
public ushort GetBerryBlenderRPMRecord(int index) => ReadUInt16LittleEndian(GetBlenderRPMSpan(index));
|
||||
public void SetBerryBlenderRPMRecord(int index, ushort value) => WriteUInt16LittleEndian(GetBlenderRPMSpan(index), value);
|
||||
|
||||
private const int EventFlag = 0x1220;
|
||||
private const int EventWork = 0x1340;
|
||||
public int EventFlagCount => 8 * 288;
|
||||
public int EventWorkCount => 0x100;
|
||||
public int EggEventFlag => 0x86;
|
||||
public int BadgeFlagStart => 0x807;
|
||||
|
||||
public bool GetEventFlag(int flagNumber)
|
||||
{
|
||||
if ((uint)flagNumber >= EventFlagCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to get ({flagNumber}) is greater than max ({EventFlagCount}).");
|
||||
return FlagUtil.GetFlag(Data, EventFlag + (flagNumber >> 3), flagNumber & 7);
|
||||
}
|
||||
|
||||
public void SetEventFlag(int flagNumber, bool value)
|
||||
{
|
||||
if ((uint)flagNumber >= EventFlagCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to set ({flagNumber}) is greater than max ({EventFlagCount}).");
|
||||
FlagUtil.SetFlag(Data, EventFlag + (flagNumber >> 3), flagNumber & 7, value);
|
||||
}
|
||||
|
||||
public ushort GetWork(int index) => ReadUInt16LittleEndian(Data[(EventWork + (index * 2))..]);
|
||||
public void SetWork(int index, ushort value) => WriteUInt16LittleEndian(Data[EventWork..][(index * 2)..], value);
|
||||
|
||||
private const int RecordOffset = 0x1540;
|
||||
private static int GetRecordOffset(RecID3RuSa record)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)record, (uint)RecID3RuSa.NUM_GAME_STATS);
|
||||
return RecordOffset + (sizeof(uint) * (int)record);
|
||||
}
|
||||
|
||||
public uint GetRecord(int record) => GetRecord((RecID3RuSa)record);
|
||||
public void SetRecord(int record, uint value) => SetRecord((RecID3RuSa)record, value);
|
||||
public uint GetRecord(RecID3RuSa record) => ReadUInt32LittleEndian(Data[GetRecordOffset(record)..]);
|
||||
public void SetRecord(RecID3RuSa record, uint value) => WriteUInt32LittleEndian(Data[GetRecordOffset(record)..], value);
|
||||
public void AddRecord(RecID3RuSa record, uint value) => SetRecord(record, GetRecord(record) + value);
|
||||
|
||||
private Memory<byte> SecretBaseData => Raw.Slice(0x1A08, SecretBaseManager3.BaseCount * SecretBase3.SIZE);
|
||||
public SecretBaseManager3 SecretBases => new(SecretBaseData);
|
||||
|
||||
public DecorationInventory3 Decorations => new(Data.Slice(0x26A0, DecorationInventory3.SIZE));
|
||||
|
||||
private Span<byte> SwarmData => Data.Slice(0x2AFC, Swarm3.SIZE);
|
||||
public Swarm3 Swarm { get => new(SwarmData.ToArray()); set => value.Data.CopyTo(SwarmData); }
|
||||
private void ClearSwarm() => SwarmData.Clear();
|
||||
public IReadOnlyList<Swarm3> DefaultSwarms => Swarm3Details.Swarms_RS;
|
||||
|
||||
public int SwarmIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
var map = Swarm.MapNum;
|
||||
for (int i = 0; i < DefaultSwarms.Count; i++)
|
||||
{
|
||||
if (DefaultSwarms[i].MapNum == map)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
set
|
||||
{
|
||||
var arr = DefaultSwarms;
|
||||
if ((uint)value >= arr.Count)
|
||||
ClearSwarm();
|
||||
else
|
||||
Swarm = arr[value];
|
||||
}
|
||||
}
|
||||
|
||||
private const int MailOffset = 0x2B4C;
|
||||
private static int GetMailOffset(int index) => (index * Mail3.SIZE) + MailOffset;
|
||||
private Span<byte> GetMailSpan(int ofs) => Data.Slice(ofs, Mail3.SIZE);
|
||||
|
||||
public Mail3 GetMail(int mailIndex)
|
||||
{
|
||||
var ofs = GetMailOffset(mailIndex);
|
||||
var span = Data.Slice(ofs, Mail3.SIZE);
|
||||
return new Mail3(span.ToArray(), ofs);
|
||||
}
|
||||
|
||||
public void SetMail(int mailIndex, Mail3 value)
|
||||
{
|
||||
var ofs = GetMailOffset(mailIndex);
|
||||
value.CopyTo(GetMailSpan(ofs));
|
||||
}
|
||||
|
||||
|
||||
private const int Painting = 0x2EFC;
|
||||
private const int PaintingCount = 5;
|
||||
private Span<byte> GetPaintingSpan(int index)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual<uint>((uint)index, PaintingCount);
|
||||
return Data.Slice(Painting + (Paintings3.SIZE * index), Paintings3.SIZE * PaintingCount);
|
||||
}
|
||||
|
||||
public Paintings3 GetPainting(int index, bool japanese) => new(GetPaintingSpan(index).ToArray(), japanese);
|
||||
public void SetPainting(int index, Paintings3 value) => value.Data.CopyTo(GetPaintingSpan(index));
|
||||
|
||||
public int DaycareOffset => 0x2F9C;
|
||||
public int DaycareSlotSize => PokeCrypto.SIZE_3STORED; // mail stored separate from box mons
|
||||
|
||||
public ushort DaycareSeed
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Data[0x30B4..]);
|
||||
set => WriteUInt16LittleEndian(Data[0x30B4..], value);
|
||||
}
|
||||
|
||||
public Span<byte> GiftRibbons => Data.Slice(ExternalEventData - 11, 11);
|
||||
public int ExternalEventData => 0x311B;
|
||||
public Memory<byte> RoamerData => Raw.Slice(0x3144, Roamer3.SIZE);
|
||||
|
||||
private const int OFFSET_EBERRY = 0x3160;
|
||||
private const int SIZE_EBERRY = 0x530;
|
||||
public Span<byte> EReaderBerry => Data.Slice(OFFSET_EBERRY, SIZE_EBERRY);
|
||||
|
||||
private Span<byte> MysterySpan => Data.Slice(0x3690, MysteryEvent3.SIZE);
|
||||
public Gen3MysteryData MysteryData
|
||||
{
|
||||
get => new MysteryEvent3(MysterySpan.ToArray());
|
||||
set => value.Data.CopyTo(MysterySpan);
|
||||
}
|
||||
|
||||
private Span<byte> RecordSpan => Data.Slice(0x3A7C, RecordMixing3Gift.SIZE);
|
||||
public RecordMixing3Gift RecordMixingGift
|
||||
{
|
||||
get => new(RecordSpan.ToArray());
|
||||
set => value.Data.CopyTo(RecordSpan);
|
||||
}
|
||||
|
||||
public int SeenOffset3 => 0x3A8C;
|
||||
}
|
||||
67
PKHeX.Core/Saves/Blocks/Gen3/SaveBlock3SmallE.cs
Normal file
67
PKHeX.Core/Saves/Blocks/Gen3/SaveBlock3SmallE.cs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed record SaveBlock3SmallE(Memory<byte> Raw) : ISaveBlock3SmallExpansion, ISaveBlock3SmallHoenn
|
||||
{
|
||||
public Span<byte> Data => Raw.Span;
|
||||
|
||||
public Span<byte> OriginalTrainerTrash => Data[..8];
|
||||
public byte Gender { get => Data[8]; set => Data[8] = value; }
|
||||
public uint ID32 { get => ReadUInt32LittleEndian(Data[0x0A..]); set => WriteUInt32LittleEndian(Data[0x0A..], value); }
|
||||
public ushort TID16 { get => ReadUInt16LittleEndian(Data[0xA..]); set => WriteUInt16LittleEndian(Data[0xA..], value); }
|
||||
public ushort SID16 { get => ReadUInt16LittleEndian(Data[0xC..]); set => WriteUInt16LittleEndian(Data[0xC..], value); }
|
||||
public int PlayedHours { get => ReadUInt16LittleEndian(Data[0xE..]); set => WriteUInt16LittleEndian(Data[0xE..], (ushort)value); }
|
||||
public int PlayedMinutes { get => Data[0x10]; set => Data[0x10] = (byte)value; }
|
||||
public int PlayedSeconds { get => Data[0x11]; set => Data[0x11] = (byte)value; }
|
||||
public byte PlayedFrames { get => Data[0x12]; set => Data[0x12] = value; }
|
||||
|
||||
public byte OptionsButtonMode { get => Data[0x13]; set => Data[0x13] = value; }
|
||||
private uint OptionsConfig { get => ReadUInt32LittleEndian(Data[0x14..]); set => WriteUInt32LittleEndian(Data[0x14..], value); }
|
||||
public int TextSpeed { get => (int)(OptionsConfig & 0b11); set => OptionsConfig = (uint)((byte)value & 0b11) | (OptionsConfig & ~0b11u); }
|
||||
public byte OptionWindowFrame { get => (byte)((OptionsConfig >> 2) & 0b11111); set => OptionsConfig = (uint)((value & 0b11111) << 2) | (OptionsConfig & ~(0b11111u << 2)); }
|
||||
public bool OptionSoundStereo { get => (OptionsConfig & 0b100000) != 0; set => OptionsConfig = value ? (OptionsConfig | 0b100000) : (OptionsConfig & ~0b100000u); }
|
||||
public bool OptionBattleStyle { get => (OptionsConfig & 0b1000000) != 0; set => OptionsConfig = value ? (OptionsConfig | 0b1000000) : (OptionsConfig & ~0b1000000u); }
|
||||
public bool OptionBattleScene { get => (OptionsConfig & 0b10000000) != 0; set => OptionsConfig = value ? (OptionsConfig | 0b10000000) : (OptionsConfig & ~0b10000000u); }
|
||||
public bool OptionIsRegionMapZoom { get => (OptionsConfig & 0b100000000) != 0; set => OptionsConfig = value ? (OptionsConfig | 0b100000000) : (OptionsConfig & ~0b100000000u); }
|
||||
|
||||
public byte PokedexSort { get => Data[0x18]; set => Data[0x18] = value; }
|
||||
public byte PokedexMode { get => Data[0x19]; set => Data[0x19] = value; }
|
||||
public byte PokedexNationalMagicRSE { get => Data[0x1A]; set => Data[0x1A] = value; }
|
||||
public byte PokedexNationalMagicFRLG { get => Data[0x1B]; set => Data[0x1B] = value; }
|
||||
public uint DexPIDUnown { get => ReadUInt32LittleEndian(Data[0x1C..]); set => WriteUInt32LittleEndian(Data[0x1C..], value); }
|
||||
public uint DexPIDSpinda { get => ReadUInt32LittleEndian(Data[0x20..]); set => WriteUInt32LittleEndian(Data[0x20..], value); }
|
||||
|
||||
private Span<byte> ClockInitialSpan => Data.Slice(0x098, RTC3.Size);
|
||||
private Span<byte> ClockElapsedSpan => Data.Slice(0x0A0, RTC3.Size);
|
||||
public RTC3 ClockInitial { get => new(ClockInitialSpan.ToArray()); set => value.Data.CopyTo(ClockInitialSpan); }
|
||||
public RTC3 ClockElapsed { get => new(ClockElapsedSpan.ToArray()); set => value.Data.CopyTo(ClockElapsedSpan); }
|
||||
|
||||
public uint LinkFlags { get => ReadUInt32LittleEndian(Data[0x0A8..]); set => WriteUInt32LittleEndian(Data[0x0A8..], value); }
|
||||
public uint SecurityKey { get => ReadUInt32LittleEndian(Data[0x0AC..]); set => WriteUInt32LittleEndian(Data[0x0AC..], value); }
|
||||
|
||||
// 0xB0: Player Apprentice
|
||||
// 0xDC: Apprentices
|
||||
|
||||
// 0x1EC: Berry Crush
|
||||
public ushort GetBerryPressSpeed([Range(0, 3)] int index) => ReadUInt16LittleEndian(Data[(0x1EC + (index * 2))..]);
|
||||
public void SetBerryPressSpeed([Range(0, 3)] int index, ushort value) => WriteUInt16LittleEndian(Data[(0x1EC + (index * 2))..], value);
|
||||
public uint BerryPowder { get => ReadUInt32LittleEndian(Data[0x1F4..]) ^ SecurityKey; set => WriteUInt32LittleEndian(Data[0x1F4..], value ^ SecurityKey); }
|
||||
public ushort JoyfulJumpInRow { get => ReadUInt16LittleEndian(Data[0x1FC..]); set => WriteUInt16LittleEndian(Data[0x1FC..], Math.Min((ushort)9999, value)); }
|
||||
public ushort JoyfulJump5InRow { get => ReadUInt16LittleEndian(Data[0x200..]); set => WriteUInt16LittleEndian(Data[0x200..], Math.Min((ushort)9999, value)); }
|
||||
public ushort JoyfulJumpGamesMaxPlayers { get => ReadUInt16LittleEndian(Data[0x202..]); set => WriteUInt16LittleEndian(Data[0x202..], Math.Min((ushort)9999, value)); }
|
||||
public uint JoyfulJumpScore { get => ReadUInt16LittleEndian(Data[0x208..]); set => WriteUInt32LittleEndian(Data[0x208..], Math.Min(99990, value)); }
|
||||
public uint JoyfulBerriesScore { get => ReadUInt16LittleEndian(Data[0x20C..]); set => WriteUInt32LittleEndian(Data[0x20C..], Math.Min(99990, value)); }
|
||||
public ushort JoyfulBerriesInRow { get => ReadUInt16LittleEndian(Data[0x210..]); set => WriteUInt16LittleEndian(Data[0x210..], Math.Min((ushort)9999, value)); }
|
||||
public ushort JoyfulBerries5InRow { get => ReadUInt16LittleEndian(Data[0x212..]); set => WriteUInt16LittleEndian(Data[0x212..], Math.Min((ushort)9999, value)); }
|
||||
|
||||
// Battle Frontier: 0x64C
|
||||
public Span<byte> EReaderTrainer => Data.Slice(0xBEC, 0xBC);
|
||||
|
||||
public BattleFrontier3 BattleFrontier => new(Data.Slice(0xCDC, BattleFrontier3.SIZE));
|
||||
|
||||
public ushort BP { get => ReadUInt16LittleEndian(Data[0xEB8..]); set => WriteUInt16LittleEndian(Data[0xEB8..], Math.Min((ushort)9999, value)); }
|
||||
public ushort BPEarned { get => ReadUInt16LittleEndian(Data[0xEBA..]); set => WriteUInt16LittleEndian(Data[0xEBA..], value); }
|
||||
}
|
||||
79
PKHeX.Core/Saves/Blocks/Gen3/SaveBlock3SmallFRLG.cs
Normal file
79
PKHeX.Core/Saves/Blocks/Gen3/SaveBlock3SmallFRLG.cs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed record SaveBlock3SmallFRLG(Memory<byte> Raw) : ISaveBlock3SmallExpansion
|
||||
{
|
||||
public Span<byte> Data => Raw.Span;
|
||||
|
||||
public Span<byte> OriginalTrainerTrash => Data[..8];
|
||||
public byte Gender { get => Data[8]; set => Data[8] = value; }
|
||||
public uint ID32 { get => ReadUInt32LittleEndian(Data[0x0A..]); set => WriteUInt32LittleEndian(Data[0x0A..], value); }
|
||||
public ushort TID16 { get => ReadUInt16LittleEndian(Data[0xA..]); set => WriteUInt16LittleEndian(Data[0xA..], value); }
|
||||
public ushort SID16 { get => ReadUInt16LittleEndian(Data[0xC..]); set => WriteUInt16LittleEndian(Data[0xC..], value); }
|
||||
public int PlayedHours { get => ReadUInt16LittleEndian(Data[0xE..]); set => WriteUInt16LittleEndian(Data[0xE..], (ushort)value); }
|
||||
public int PlayedMinutes { get => Data[0x10]; set => Data[0x10] = (byte)value; }
|
||||
public int PlayedSeconds { get => Data[0x11]; set => Data[0x11] = (byte)value; }
|
||||
public byte PlayedFrames { get => Data[0x12]; set => Data[0x12] = value; }
|
||||
|
||||
public byte OptionsButtonMode { get => Data[0x13]; set => Data[0x13] = value; }
|
||||
private uint OptionsConfig { get => ReadUInt32LittleEndian(Data[0x14..]); set => WriteUInt32LittleEndian(Data[0x14..], value); }
|
||||
public int TextSpeed { get => (int)(OptionsConfig & 0b11); set => OptionsConfig = (uint)((byte)value & 0b11) | (OptionsConfig & ~0b11u); }
|
||||
public byte OptionWindowFrame { get => (byte)((OptionsConfig >> 2) & 0b11111); set => OptionsConfig = (uint)((value & 0b11111) << 2) | (OptionsConfig & ~(0b11111u << 2)); }
|
||||
public bool OptionSoundStereo { get => (OptionsConfig & 0b100000) != 0; set => OptionsConfig = value ? (OptionsConfig | 0b100000) : (OptionsConfig & ~0b100000u); }
|
||||
public bool OptionBattleStyle { get => (OptionsConfig & 0b1000000) != 0; set => OptionsConfig = value ? (OptionsConfig | 0b1000000) : (OptionsConfig & ~0b1000000u); }
|
||||
public bool OptionBattleScene { get => (OptionsConfig & 0b10000000) != 0; set => OptionsConfig = value ? (OptionsConfig | 0b10000000) : (OptionsConfig & ~0b10000000u); }
|
||||
public bool OptionIsRegionMapZoom { get => (OptionsConfig & 0b100000000) != 0; set => OptionsConfig = value ? (OptionsConfig | 0b100000000) : (OptionsConfig & ~0b100000000u); }
|
||||
|
||||
public byte PokedexSort { get => Data[0x18]; set => Data[0x18] = value; }
|
||||
public byte PokedexMode { get => Data[0x19]; set => Data[0x19] = value; }
|
||||
public byte PokedexNationalMagicRSE { get => Data[0x1A]; set => Data[0x1A] = value; }
|
||||
public byte PokedexNationalMagicFRLG { get => Data[0x1B]; set => Data[0x1B] = value; }
|
||||
public uint DexPIDUnown { get => ReadUInt32LittleEndian(Data[0x1C..]); set => WriteUInt32LittleEndian(Data[0x1C..], value); }
|
||||
public uint DexPIDSpinda { get => ReadUInt32LittleEndian(Data[0x20..]); set => WriteUInt32LittleEndian(Data[0x20..], value); }
|
||||
|
||||
public uint LinkFlags { get => ReadUInt32LittleEndian(Data[0x0A8..]); set => WriteUInt32LittleEndian(Data[0x0A8..], value); }
|
||||
|
||||
|
||||
public bool DummyFlagTrue
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Data[0x0AC..]) != 0;
|
||||
set => WriteUInt16LittleEndian(Data[0x0AC..], value ? (ushort)1 : (ushort)0);
|
||||
}
|
||||
|
||||
public bool DummyFlagFalse
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Data[0x0AE..]) == 0;
|
||||
set => WriteUInt16LittleEndian(Data[0x0AE..], value ? (ushort)0 : (ushort)1);
|
||||
}
|
||||
|
||||
public void FixDummyFlags()
|
||||
{
|
||||
DummyFlagTrue = true;
|
||||
DummyFlagFalse = false;
|
||||
}
|
||||
|
||||
// Battle Tower: 0xB0
|
||||
|
||||
public Span<byte> EReaderTrainer => Data.Slice(0x4A0, 0xBC);
|
||||
|
||||
// 0xAF0: Berry Crush
|
||||
public ushort GetBerryPressSpeed([Range(0,3)] int index) => ReadUInt16LittleEndian(Data[(0xAF0 + (index * 2))..]);
|
||||
public void SetBerryPressSpeed([Range(0, 3)] int index, ushort value) => WriteUInt16LittleEndian(Data[(0xAF0 + (index * 2))..], value);
|
||||
public uint BerryPowder { get => ReadUInt32LittleEndian(Data[0xAF8..]) ^ SecurityKey; set => WriteUInt32LittleEndian(Data[0xAF8..], value ^ SecurityKey); }
|
||||
public ushort JoyfulJumpInRow { get => ReadUInt16LittleEndian(Data[0xB00..]); set => WriteUInt16LittleEndian(Data[0xB00..], Math.Min((ushort)9999, value)); }
|
||||
public ushort JoyfulJump5InRow { get => ReadUInt16LittleEndian(Data[0xB04..]); set => WriteUInt16LittleEndian(Data[0xB04..], Math.Min((ushort)9999, value)); }
|
||||
public ushort JoyfulJumpGamesMaxPlayers { get => ReadUInt16LittleEndian(Data[0xB06..]); set => WriteUInt16LittleEndian(Data[0xB06..], Math.Min((ushort)9999, value)); }
|
||||
public uint JoyfulJumpScore { get => ReadUInt16LittleEndian(Data[0xB0C..]); set => WriteUInt32LittleEndian(Data[0xB0C..], Math.Min(99990, value)); }
|
||||
public uint JoyfulBerriesScore { get => ReadUInt16LittleEndian(Data[0xB10..]); set => WriteUInt32LittleEndian(Data[0xB10..], Math.Min(99990, value)); }
|
||||
public ushort JoyfulBerriesInRow { get => ReadUInt16LittleEndian(Data[0xB14..]); set => WriteUInt16LittleEndian(Data[0xB14..], Math.Min((ushort)9999, value)); }
|
||||
public ushort JoyfulBerries5InRow { get => ReadUInt16LittleEndian(Data[0xB16..]); set => WriteUInt16LittleEndian(Data[0xB16..], Math.Min((ushort)9999, value)); }
|
||||
|
||||
public uint SecurityKey
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Data[0xF20..]);
|
||||
set => WriteUInt32LittleEndian(Data[0xF20..], value);
|
||||
}
|
||||
}
|
||||
45
PKHeX.Core/Saves/Blocks/Gen3/SaveBlock3SmallRS.cs
Normal file
45
PKHeX.Core/Saves/Blocks/Gen3/SaveBlock3SmallRS.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed record SaveBlock3SmallRS(Memory<byte> Raw) : ISaveBlock3SmallHoenn
|
||||
{
|
||||
public Span<byte> Data => Raw.Span;
|
||||
|
||||
public Span<byte> OriginalTrainerTrash => Data[..8];
|
||||
public byte Gender { get => Data[8]; set => Data[8] = value; }
|
||||
public uint ID32 { get => ReadUInt32LittleEndian(Data[0x0A..]); set => WriteUInt32LittleEndian(Data[0x0A..], value); }
|
||||
public ushort TID16 { get => ReadUInt16LittleEndian(Data[0xA..]); set => WriteUInt16LittleEndian(Data[0xA..], value); }
|
||||
public ushort SID16 { get => ReadUInt16LittleEndian(Data[0xC..]); set => WriteUInt16LittleEndian(Data[0xC..], value); }
|
||||
public int PlayedHours { get => ReadUInt16LittleEndian(Data[0xE..]); set => WriteUInt16LittleEndian(Data[0xE..], (ushort)value); }
|
||||
public int PlayedMinutes { get => Data[0x10]; set => Data[0x10] = (byte)value; }
|
||||
public int PlayedSeconds { get => Data[0x11]; set => Data[0x11] = (byte)value; }
|
||||
public byte PlayedFrames { get => Data[0x12]; set => Data[0x12] = value; }
|
||||
|
||||
public byte OptionsButtonMode { get => Data[0x13]; set => Data[0x13] = value; }
|
||||
private uint OptionsConfig { get => ReadUInt32LittleEndian(Data[0x14..]); set => WriteUInt32LittleEndian(Data[0x14..], value); }
|
||||
public int TextSpeed { get => (int)(OptionsConfig & 0b11); set => OptionsConfig = (uint)((byte)value & 0b11) | (OptionsConfig & ~0b11u); }
|
||||
public byte OptionWindowFrame { get => (byte)((OptionsConfig >> 2) & 0b11111); set => OptionsConfig = (uint)((value & 0b11111) << 2) | (OptionsConfig & ~(0b11111u << 2)); }
|
||||
public bool OptionSoundStereo { get => (OptionsConfig & 0b100000) != 0; set => OptionsConfig = value ? (OptionsConfig | 0b100000) : (OptionsConfig & ~0b100000u); }
|
||||
public bool OptionBattleStyle { get => (OptionsConfig & 0b1000000) != 0; set => OptionsConfig = value ? (OptionsConfig | 0b1000000) : (OptionsConfig & ~0b1000000u); }
|
||||
public bool OptionBattleScene { get => (OptionsConfig & 0b10000000) != 0; set => OptionsConfig = value ? (OptionsConfig | 0b10000000) : (OptionsConfig & ~0b10000000u); }
|
||||
public bool OptionIsRegionMapZoom { get => (OptionsConfig & 0b100000000) != 0; set => OptionsConfig = value ? (OptionsConfig | 0b100000000) : (OptionsConfig & ~0b100000000u); }
|
||||
|
||||
public byte PokedexSort { get => Data[0x18]; set => Data[0x18] = value; }
|
||||
public byte PokedexMode { get => Data[0x19]; set => Data[0x19] = value; }
|
||||
public byte PokedexNationalMagicRSE { get => Data[0x1A]; set => Data[0x1A] = value; }
|
||||
public byte PokedexNationalMagicFRLG { get => Data[0x1B]; set => Data[0x1B] = value; }
|
||||
public uint DexPIDUnown { get => ReadUInt32LittleEndian(Data[0x1C..]); set => WriteUInt32LittleEndian(Data[0x1C..], value); }
|
||||
public uint DexPIDSpinda { get => ReadUInt32LittleEndian(Data[0x20..]); set => WriteUInt32LittleEndian(Data[0x20..], value); }
|
||||
|
||||
private Span<byte> ClockInitialSpan => Data.Slice(0x098, RTC3.Size);
|
||||
private Span<byte> ClockElapsedSpan => Data.Slice(0x0A0, RTC3.Size);
|
||||
public RTC3 ClockInitial { get => new(ClockInitialSpan.ToArray()); set => value.Data.CopyTo(ClockInitialSpan); }
|
||||
public RTC3 ClockElapsed { get => new(ClockElapsedSpan.ToArray()); set => value.Data.CopyTo(ClockElapsedSpan); }
|
||||
|
||||
public uint SecurityKey => 0;
|
||||
|
||||
// 0xA8: Battle Tower
|
||||
public Span<byte> EReaderTrainer => Data.Slice(0x498, 0xBC);
|
||||
}
|
||||
|
|
@ -293,13 +293,13 @@ public override ushort TID16
|
|||
|
||||
public override ushort SID16 { get => 0; set { } }
|
||||
|
||||
public string Rival
|
||||
public string RivalName
|
||||
{
|
||||
get => GetString(Data.Slice(Offsets.Rival, MaxStringLengthTrainer));
|
||||
set => SetString(Data.Slice(Offsets.Rival, MaxStringLengthTrainer), value, MaxStringLengthTrainer, StringConverterOption.Clear50);
|
||||
}
|
||||
|
||||
public Span<byte> RivalTrash { get => Data.Slice(Offsets.Rival, StringLength); set { if (value.Length == StringLength) value.CopyTo(Data[Offsets.Rival..]); } }
|
||||
public Span<byte> RivalNameTrash { get => Data.Slice(Offsets.Rival, StringLength); set { if (value.Length == StringLength) value.CopyTo(Data[Offsets.Rival..]); } }
|
||||
|
||||
public byte RivalStarter { get => Data[Offsets.Starter - 2]; set => Data[Offsets.Starter - 2] = value; }
|
||||
public byte Starter { get => Data[Offsets.Starter]; set => Data[Offsets.Starter] = value; }
|
||||
|
|
|
|||
|
|
@ -325,13 +325,13 @@ public Span<byte> OriginalTrainerTrash
|
|||
set { if (value.Length == StringLength) value.CopyTo(Data[(Offsets.Trainer1 + 2)..]); }
|
||||
}
|
||||
|
||||
public string Rival
|
||||
public string RivalName
|
||||
{
|
||||
get => GetString(Data.Slice(Offsets.Rival, (Korean ? 2 : 1) * MaxStringLengthTrainer));
|
||||
set => SetString(Data.Slice(Offsets.Rival, (Korean ? 2 : 1) * MaxStringLengthTrainer), value, 8, StringConverterOption.Clear50);
|
||||
}
|
||||
|
||||
public Span<byte> RivalTrash
|
||||
public Span<byte> RivalNameTrash
|
||||
{
|
||||
get => Data.Slice(Offsets.Rival, StringLength);
|
||||
set { if (value.Length == StringLength) value.CopyTo(Data[Offsets.Rival..]); }
|
||||
|
|
|
|||
|
|
@ -18,8 +18,14 @@ public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37, IBoxDetai
|
|||
public bool Japanese { get; }
|
||||
public bool Korean => false;
|
||||
|
||||
public bool IsVirtualConsole => State.Exportable && Metadata.FileName is { } s && s.Contains(".sav")
|
||||
&& (s.StartsWith("FireRed_", StringComparison.Ordinal) || s.StartsWith("LeafGreen_")); // default to Mainline-Era for non-exportable
|
||||
public bool IsVirtualConsole => State.Exportable && Metadata.FileName is { } s && IsVirtualConsoleFileName(s); // default to Mainline-Era for non-exportable
|
||||
|
||||
public static bool IsVirtualConsoleFileName(string s)
|
||||
{
|
||||
if (!s.Contains(".sav"))
|
||||
return false;
|
||||
return s.StartsWith("FireRed_") || s.StartsWith("LeafGreen_");
|
||||
}
|
||||
|
||||
// Similar to future games, the Generation 3 Mainline save files are comprised of separate objects:
|
||||
// Object 1 - Small, containing misc configuration data & the Pokédex.
|
||||
|
|
@ -46,6 +52,8 @@ public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37, IBoxDetai
|
|||
public Span<byte> Small => SmallBuffer.Span;
|
||||
public Span<byte> Large => LargeBuffer.Span;
|
||||
public Span<byte> Storage => StorageBuffer.Span;
|
||||
public abstract ISaveBlock3Small SmallBlock { get; }
|
||||
public abstract ISaveBlock3Large LargeBlock { get; }
|
||||
|
||||
private readonly int ActiveSlot;
|
||||
public sealed override int Language { get; set; }
|
||||
|
|
@ -177,14 +185,11 @@ public void WriteBothSaveSlots(Span<byte> data)
|
|||
public sealed override ushort MaxMoveID => Legal.MaxMoveID_3;
|
||||
public sealed override ushort MaxSpeciesID => Legal.MaxSpeciesID_3;
|
||||
public sealed override int MaxAbilityID => Legal.MaxAbilityID_3;
|
||||
public override int MaxItemID => Legal.MaxItemID_3;
|
||||
public sealed override int MaxBallID => Legal.MaxBallID_3;
|
||||
public sealed override GameVersion MaxGameID => Legal.MaxGameID_3;
|
||||
|
||||
public abstract int EventFlagCount { get; }
|
||||
public abstract int EventWorkCount { get; }
|
||||
protected abstract int EventFlag { get; }
|
||||
protected abstract int EventWork { get; }
|
||||
public int EventFlagCount => LargeBlock.EventFlagCount;
|
||||
public int EventWorkCount => LargeBlock.EventWorkCount;
|
||||
|
||||
/// <summary>
|
||||
/// Force loads a new <see cref="SAV3"/> object to the requested <see cref="version"/>.
|
||||
|
|
@ -210,13 +215,15 @@ public void WriteBothSaveSlots(Span<byte> data)
|
|||
public sealed override int MaxMoney => 999999;
|
||||
|
||||
public sealed override bool HasParty => true;
|
||||
public sealed override int PartyCount { get => LargeBlock.PartyCount; protected set => LargeBlock.PartyCount = (byte)value; }
|
||||
public sealed override int GetPartyOffset(int slot) => SIZE_PARTY * slot;
|
||||
|
||||
public sealed override bool IsPKMPresent(ReadOnlySpan<byte> data) => EntityDetection.IsPresentGBA(data);
|
||||
protected sealed override PK3 GetPKM(byte[] data) => new(data);
|
||||
protected sealed override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray3(data);
|
||||
|
||||
protected sealed override Span<byte> BoxBuffer => Storage;
|
||||
protected sealed override Span<byte> PartyBuffer => Large;
|
||||
protected sealed override Span<byte> PartyBuffer => LargeBlock.PartyBuffer;
|
||||
|
||||
private const int COUNT_BOX = 14;
|
||||
private const int COUNT_SLOTSPERBOX = 30;
|
||||
|
|
@ -328,7 +335,7 @@ public sealed override string ChecksumInfo
|
|||
|
||||
public static bool IsMail(int itemID) => (uint)(itemID - 121) <= (132 - 121);
|
||||
|
||||
protected override void SetPartyValues(PKM pk, bool isParty)
|
||||
protected sealed override void SetPartyValues(PKM pk, bool isParty)
|
||||
{
|
||||
if (pk is not PK3 p3)
|
||||
return;
|
||||
|
|
@ -341,170 +348,82 @@ protected override void SetPartyValues(PKM pk, bool isParty)
|
|||
base.SetPartyValues(pk, isParty);
|
||||
}
|
||||
|
||||
public abstract uint SecurityKey { get; set; }
|
||||
|
||||
public Span<byte> OriginalTrainerTrash => Small[..8];
|
||||
|
||||
public sealed override string OT
|
||||
{
|
||||
get
|
||||
{
|
||||
int len = Japanese ? 5 : MaxStringLengthTrainer;
|
||||
return GetString(OriginalTrainerTrash[..len]);
|
||||
return GetString(SmallBlock.OriginalTrainerTrash[..len]);
|
||||
}
|
||||
set
|
||||
{
|
||||
int len = Japanese ? 5 : MaxStringLengthTrainer;
|
||||
SetString(OriginalTrainerTrash[..len], value, len, StringConverterOption.ClearFF); // match the game-init FF terminating pattern
|
||||
SetString(SmallBlock.OriginalTrainerTrash[..len], value, len, StringConverterOption.None); // Preserve original pattern
|
||||
}
|
||||
}
|
||||
|
||||
public sealed override byte Gender
|
||||
{
|
||||
get => Small[8];
|
||||
set => Small[8] = value;
|
||||
get => SmallBlock.Gender;
|
||||
set => SmallBlock.Gender = value;
|
||||
}
|
||||
|
||||
public sealed override uint ID32
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Small[0x0A..]);
|
||||
set => WriteUInt32LittleEndian(Small[0x0A..], value);
|
||||
get => SmallBlock.ID32;
|
||||
set => SmallBlock.ID32 = value;
|
||||
}
|
||||
|
||||
public sealed override ushort TID16
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Small[0xA..]);
|
||||
set => WriteUInt16LittleEndian(Small[0xA..], value);
|
||||
get => SmallBlock.TID16;
|
||||
set => SmallBlock.TID16 = value;
|
||||
}
|
||||
|
||||
public sealed override ushort SID16
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Small[0xC..]);
|
||||
set => WriteUInt16LittleEndian(Small[0xC..], value);
|
||||
get => SmallBlock.SID16;
|
||||
set => SmallBlock.SID16 = value;
|
||||
}
|
||||
|
||||
public sealed override int PlayedHours
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Small[0xE..]);
|
||||
set => WriteUInt16LittleEndian(Small[0xE..], (ushort)value);
|
||||
get => SmallBlock.PlayedHours;
|
||||
set => SmallBlock.PlayedHours = value;
|
||||
}
|
||||
|
||||
public sealed override int PlayedMinutes
|
||||
{
|
||||
get => Small[0x10];
|
||||
set => Small[0x10] = (byte)value;
|
||||
get => SmallBlock.PlayedMinutes;
|
||||
set => SmallBlock.PlayedMinutes = value;
|
||||
}
|
||||
|
||||
public sealed override int PlayedSeconds
|
||||
{
|
||||
get => Small[0x11];
|
||||
set => Small[0x11] = (byte)value;
|
||||
}
|
||||
|
||||
public byte PlayedFrames
|
||||
{
|
||||
get => Small[0x12];
|
||||
set => Small[0x12] = value;
|
||||
}
|
||||
|
||||
public byte OptionsButtonMode
|
||||
{
|
||||
get => Small[0x13];
|
||||
set => Small[0x13] = value;
|
||||
}
|
||||
|
||||
private uint OptionsConfig
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Small[0x14..]);
|
||||
set => WriteUInt32LittleEndian(Small[0x14..], value);
|
||||
}
|
||||
|
||||
// 2 bits: Text Speed
|
||||
// 5 bits: Window Frame
|
||||
// 1 bit: sound
|
||||
// 1 bit: battle style (shift vs set)
|
||||
// 1 bit: battle scene off toggle (animations enabled/disabled)
|
||||
// 1 bit: regionMapZoom (on/off)
|
||||
// 4 bits unused
|
||||
// 16 bits unused
|
||||
public int TextSpeed
|
||||
{
|
||||
get => (int)(OptionsConfig & 0b11);
|
||||
set => OptionsConfig = (uint)((byte)value & 0b11) | (OptionsConfig & ~0b11u);
|
||||
}
|
||||
|
||||
public byte OptionWindowFrame
|
||||
{
|
||||
get => (byte)((OptionsConfig >> 2) & 0b11111);
|
||||
set => OptionsConfig = (uint)((value & 0b11111) << 2) | (OptionsConfig & ~(0b11111u << 2));
|
||||
}
|
||||
|
||||
public bool OptionSoundStereo
|
||||
{
|
||||
get => (OptionsConfig & 0b100000) != 0;
|
||||
set => OptionsConfig = value ? (OptionsConfig | 0b100000) : (OptionsConfig & ~0b100000u);
|
||||
}
|
||||
|
||||
public bool OptionBattleStyle
|
||||
{
|
||||
get => (OptionsConfig & 0b1000000) != 0;
|
||||
set => OptionsConfig = value ? (OptionsConfig | 0b1000000) : (OptionsConfig & ~0b1000000u);
|
||||
}
|
||||
|
||||
public bool OptionBattleScene
|
||||
{
|
||||
get => (OptionsConfig & 0b10000000) != 0;
|
||||
set => OptionsConfig = value ? (OptionsConfig | 0b10000000) : (OptionsConfig & ~0b10000000u);
|
||||
}
|
||||
|
||||
public bool OptionIsRegionMapZoom
|
||||
{
|
||||
get => (OptionsConfig & 0b100000000) != 0;
|
||||
set => OptionsConfig = value ? (OptionsConfig | 0b100000000) : (OptionsConfig & ~0b100000000u);
|
||||
}
|
||||
|
||||
public ushort X
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Large);
|
||||
set => WriteUInt16LittleEndian(Large, value);
|
||||
}
|
||||
|
||||
public ushort Y
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Large[2..]);
|
||||
set => WriteUInt16LittleEndian(Large[2..], value);
|
||||
get => SmallBlock.PlayedSeconds;
|
||||
set => SmallBlock.PlayedSeconds = value;
|
||||
}
|
||||
|
||||
#region Event Flag/Event Work
|
||||
public bool GetEventFlag(int flagNumber)
|
||||
{
|
||||
if ((uint)flagNumber >= EventFlagCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to get ({flagNumber}) is greater than max ({EventFlagCount}).");
|
||||
return GetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7);
|
||||
}
|
||||
public bool GetEventFlag(int flagNumber) => LargeBlock.GetEventFlag(flagNumber);
|
||||
public void SetEventFlag(int flagNumber, bool value) => LargeBlock.SetEventFlag(flagNumber, value);
|
||||
|
||||
public void SetEventFlag(int flagNumber, bool value)
|
||||
{
|
||||
if ((uint)flagNumber >= EventFlagCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to set ({flagNumber}) is greater than max ({EventFlagCount}).");
|
||||
SetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7, value);
|
||||
}
|
||||
|
||||
public ushort GetWork(int index) => ReadUInt16LittleEndian(Large[(EventWork + (index * 2))..]);
|
||||
public void SetWork(int index, ushort value) => WriteUInt16LittleEndian(Large[EventWork..][(index * 2)..], value);
|
||||
public ushort GetWork(int index) => LargeBlock.GetWork(index);
|
||||
public void SetWork(int index, ushort value) => LargeBlock.SetWork(index, value);
|
||||
#endregion
|
||||
|
||||
public sealed override bool GetFlag(int offset, int bitIndex) => GetFlag(Large, offset, bitIndex);
|
||||
public sealed override void SetFlag(int offset, int bitIndex, bool value) => SetFlag(Large, offset, bitIndex, value);
|
||||
public uint GetRecord(int record) => LargeBlock.GetRecord(record) ^ SmallBlock.SecurityKey;
|
||||
public void SetRecord(int record, uint value) => LargeBlock.SetRecord(record, value ^ SmallBlock.SecurityKey);
|
||||
|
||||
protected abstract int BadgeFlagStart { get; }
|
||||
public abstract uint Coin { get; set; }
|
||||
|
||||
public int Badges
|
||||
{
|
||||
get
|
||||
{
|
||||
int startFlag = BadgeFlagStart;
|
||||
int startFlag = LargeBlock.BadgeFlagStart;
|
||||
int val = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
|
|
@ -516,30 +435,27 @@ public int Badges
|
|||
}
|
||||
set
|
||||
{
|
||||
int startFlag = BadgeFlagStart;
|
||||
int startFlag = LargeBlock.BadgeFlagStart;
|
||||
for (int i = 0; i < 8; i++)
|
||||
SetEventFlag(startFlag + i, (value & (1 << i)) != 0);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract int PokeDex { get; }
|
||||
public override bool HasPokeDex => true;
|
||||
public sealed override bool HasPokeDex => true;
|
||||
|
||||
public int DaycareSlotCount => 2;
|
||||
protected abstract int DaycareSlotSize { get; }
|
||||
protected abstract int DaycareOffset { get; }
|
||||
protected abstract int GetDaycareEXPOffset(int slot);
|
||||
public Memory<byte> GetDaycareSlot(int slot) => LargeBuffer.Slice(GetDaycareSlotOffset(slot), DaycareSlotSize);
|
||||
public Memory<byte> GetDaycareSlot(int slot) => LargeBuffer.Slice(GetDaycareSlotOffset(slot), LargeBlock.DaycareSlotSize);
|
||||
public uint GetDaycareEXP(int index) => ReadUInt32LittleEndian(Large[GetDaycareEXPOffset(index)..]);
|
||||
public void SetDaycareEXP(int index, uint value) => WriteUInt32LittleEndian(Large[GetDaycareEXPOffset(index)..], value);
|
||||
public bool IsDaycareOccupied(int slot) => IsPKMPresent(Large[GetDaycareSlotOffset(slot)..]);
|
||||
public void SetDaycareOccupied(int slot, bool occupied) { /* todo */ }
|
||||
public int GetDaycareSlotOffset(int slot) => DaycareOffset + (slot * DaycareSlotSize);
|
||||
protected abstract int EggEventFlag { get; }
|
||||
public bool IsEggAvailable { get => GetEventFlag(EggEventFlag); set => SetEventFlag(EggEventFlag, value); }
|
||||
public int GetDaycareSlotOffset(int slot) => LargeBlock.DaycareOffset + (slot * LargeBlock.DaycareSlotSize);
|
||||
public bool IsEggAvailable { get => GetEventFlag(LargeBlock.EggEventFlag); set => SetEventFlag(LargeBlock.EggEventFlag, value); }
|
||||
|
||||
#region Storage
|
||||
public sealed override int GetBoxOffset(int box) => Box + 4 + (SIZE_STORED * box * COUNT_SLOTSPERBOX);
|
||||
|
||||
public sealed override bool HasBox => true;
|
||||
|
||||
public sealed override int CurrentBox
|
||||
{
|
||||
|
|
@ -547,6 +463,8 @@ public sealed override int CurrentBox
|
|||
set => Storage[0] = (byte)value;
|
||||
}
|
||||
|
||||
public sealed override int GetBoxOffset(int box) => 4 + (SIZE_STORED * box * COUNT_SLOTSPERBOX);
|
||||
|
||||
public int GetBoxWallpaper(int box)
|
||||
{
|
||||
if (box >= COUNT_BOX)
|
||||
|
|
@ -598,25 +516,24 @@ protected sealed override void SetDex(PKM pk)
|
|||
switch (species)
|
||||
{
|
||||
case (int)Species.Unown when !GetSeen(species): // Unown
|
||||
DexPIDUnown = pk.PID;
|
||||
SmallBlock.DexPIDUnown = pk.PID;
|
||||
break;
|
||||
case (int)Species.Spinda when !GetSeen(species): // Spinda
|
||||
DexPIDSpinda = pk.PID;
|
||||
SmallBlock.DexPIDSpinda = pk.PID;
|
||||
break;
|
||||
}
|
||||
SetCaught(species, true);
|
||||
SetSeen(species, true);
|
||||
}
|
||||
|
||||
public uint DexPIDUnown { get => ReadUInt32LittleEndian(Small[(PokeDex + 0x4)..]); set => WriteUInt32LittleEndian(Small[(PokeDex + 0x4)..], value); }
|
||||
public uint DexPIDSpinda { get => ReadUInt32LittleEndian(Small[(PokeDex + 0x8)..]); set => WriteUInt32LittleEndian(Small[(PokeDex + 0x8)..], value); }
|
||||
public int DexUnownForm => EntityPID.GetUnownForm3(DexPIDUnown);
|
||||
|
||||
private const int PokeDex = 0x18; // small
|
||||
|
||||
public sealed override bool GetCaught(ushort species)
|
||||
{
|
||||
int bit = species - 1;
|
||||
int ofs = bit >> 3;
|
||||
int caughtOffset = PokeDex + 0x10;
|
||||
const int caughtOffset = PokeDex + 0x10;
|
||||
return FlagUtil.GetFlag(Small, caughtOffset + ofs, bit & 7);
|
||||
}
|
||||
|
||||
|
|
@ -624,7 +541,7 @@ public sealed override void SetCaught(ushort species, bool caught)
|
|||
{
|
||||
int bit = species - 1;
|
||||
int ofs = bit >> 3;
|
||||
int caughtOffset = PokeDex + 0x10;
|
||||
const int caughtOffset = PokeDex + 0x10;
|
||||
FlagUtil.SetFlag(Small, caughtOffset + ofs, bit & 7, caught);
|
||||
}
|
||||
|
||||
|
|
@ -632,46 +549,37 @@ public sealed override bool GetSeen(ushort species)
|
|||
{
|
||||
int bit = species - 1;
|
||||
int ofs = bit >> 3;
|
||||
int seenOffset = PokeDex + 0x44;
|
||||
const int seenOffset = PokeDex + 0x44;
|
||||
return FlagUtil.GetFlag(Small, seenOffset + ofs, bit & 7);
|
||||
}
|
||||
|
||||
protected abstract int SeenOffset2 { get; }
|
||||
protected abstract int SeenOffset3 { get; }
|
||||
|
||||
public sealed override void SetSeen(ushort species, bool seen)
|
||||
{
|
||||
int bit = species - 1;
|
||||
int ofs = bit >> 3;
|
||||
|
||||
int seenOffset = PokeDex + 0x44;
|
||||
const int seenOffset = PokeDex + 0x44;
|
||||
FlagUtil.SetFlag(Small, seenOffset + ofs, bit & 7, seen);
|
||||
FlagUtil.SetFlag(Large, SeenOffset2 + ofs, bit & 7, seen);
|
||||
FlagUtil.SetFlag(Large, SeenOffset3 + ofs, bit & 7, seen);
|
||||
FlagUtil.SetFlag(Large, LargeBlock.SeenOffset2 + ofs, bit & 7, seen);
|
||||
FlagUtil.SetFlag(Large, LargeBlock.SeenOffset3 + ofs, bit & 7, seen);
|
||||
}
|
||||
|
||||
public byte PokedexSort
|
||||
/// <summary>
|
||||
/// In Gen 3, the seen flags are stored in three different places. Mirror them to each other to ensure consistency.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only really use this if you are allowing users to manually edit the seen flags in the first (normal) section; then trigger this on saving all.
|
||||
/// </remarks>
|
||||
public void MirrorSeenFlags()
|
||||
{
|
||||
get => Small[PokeDex + 0x01];
|
||||
set => Small[PokeDex + 0x01] = value;
|
||||
}
|
||||
|
||||
public byte PokedexMode
|
||||
{
|
||||
get => Small[PokeDex + 0x01];
|
||||
set => Small[PokeDex + 0x01] = value;
|
||||
}
|
||||
|
||||
public byte PokedexNationalMagicRSE
|
||||
{
|
||||
get => Small[PokeDex + 0x02];
|
||||
set => Small[PokeDex + 0x02] = value;
|
||||
}
|
||||
|
||||
public byte PokedexNationalMagicFRLG
|
||||
{
|
||||
get => Small[PokeDex + 0x03];
|
||||
set => Small[PokeDex + 0x03] = value;
|
||||
for (ushort species = 1; species <= Legal.MaxSpeciesID_3; species++)
|
||||
{
|
||||
int bit = species - 1;
|
||||
int ofs = bit >> 3;
|
||||
bool seen = FlagUtil.GetFlag(Small, PokeDex + 0x44 + ofs, bit & 7);
|
||||
FlagUtil.SetFlag(Large, LargeBlock.SeenOffset2 + ofs, bit & 7, seen);
|
||||
FlagUtil.SetFlag(Large, LargeBlock.SeenOffset3 + ofs, bit & 7, seen);
|
||||
}
|
||||
}
|
||||
|
||||
protected const byte PokedexNationalUnlockRSE = 0xDA;
|
||||
|
|
@ -684,32 +592,18 @@ public byte PokedexNationalMagicFRLG
|
|||
|
||||
public sealed override string GetString(ReadOnlySpan<byte> data)
|
||||
=> StringConverter3.GetString(data, Japanese);
|
||||
public override int LoadString(ReadOnlySpan<byte> data, Span<char> destBuffer)
|
||||
public sealed override int LoadString(ReadOnlySpan<byte> data, Span<char> destBuffer)
|
||||
=> StringConverter3.LoadString(data, destBuffer, Japanese);
|
||||
public sealed override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, int maxLength, StringConverterOption option)
|
||||
=> StringConverter3.SetString(destBuffer, value, maxLength, Japanese, option);
|
||||
|
||||
protected abstract int MailOffset { get; }
|
||||
public int GetMailOffset(int index) => (index * Mail3.SIZE) + MailOffset;
|
||||
public string EBerryName => GetString(LargeBlock.EReaderBerry[..7]);
|
||||
public bool IsEBerryEngima => LargeBlock.EReaderBerry[0] is 0 or 0xFF;
|
||||
|
||||
public MailDetail GetMail(int mailIndex)
|
||||
{
|
||||
var ofs = GetMailOffset(mailIndex);
|
||||
var data = Large.Slice(ofs, Mail3.SIZE).ToArray();
|
||||
return new Mail3(data, ofs);
|
||||
}
|
||||
|
||||
#region eBerry
|
||||
public abstract Span<byte> EReaderBerry();
|
||||
public string EBerryName => GetString(EReaderBerry()[..7]);
|
||||
public bool IsEBerryEngima => EReaderBerry()[0] is 0 or 0xFF;
|
||||
#endregion
|
||||
|
||||
#region eTrainer
|
||||
public abstract Span<byte> EReaderTrainer();
|
||||
#endregion
|
||||
|
||||
public abstract Gen3MysteryData MysteryData { get; set; }
|
||||
/// <summary>
|
||||
/// Indicates if the extdata sections of the save file are available for get/set.
|
||||
/// </summary>
|
||||
public bool IsFullSaveFile => Data.Length >= SaveUtil.SIZE_G3RAW;
|
||||
|
||||
/// <summary>
|
||||
/// Hall of Fame data is split across two sectors.
|
||||
|
|
@ -741,14 +635,15 @@ public void SetHallOfFameData(ReadOnlySpan<byte> value)
|
|||
/// </summary>
|
||||
public Memory<byte> GetEReaderData() => Buffer.Slice(0x1E000, SIZE_SECTOR_USED);
|
||||
|
||||
/// <summary> Only used in Emerald. </summary>
|
||||
/// <summary> Only used in Emerald for storing the Battle Video. </summary>
|
||||
public Memory<byte> GetFinalExternalData() => Buffer.Slice(0x1F000, SIZE_SECTOR_USED);
|
||||
|
||||
public bool IsCorruptPokedexFF() => MemoryMarshal.Read<ulong>(Small[0xAC..]) == ulong.MaxValue;
|
||||
|
||||
public override void CopyChangesFrom(SaveFile sav)
|
||||
public sealed override void CopyChangesFrom(SaveFile sav)
|
||||
{
|
||||
SetData(sav.Data, 0);
|
||||
if (Data.Length != 0)
|
||||
SetData(sav.Data, 0);
|
||||
var s3 = (SAV3)sav;
|
||||
SetData(Small, s3.Small);
|
||||
SetData(Large, s3.Large);
|
||||
|
|
@ -757,7 +652,7 @@ public override void CopyChangesFrom(SaveFile sav)
|
|||
|
||||
#region External Connections
|
||||
|
||||
public Span<byte> GiftRibbons => Large.Slice(ExternalEventData - 11, 11);
|
||||
public Span<byte> GiftRibbons => LargeBlock.GiftRibbons;
|
||||
|
||||
public void GiftRibbonsImport(ReadOnlySpan<byte> trade)
|
||||
{
|
||||
|
|
@ -773,7 +668,7 @@ public void GiftRibbonsImport(ReadOnlySpan<byte> trade)
|
|||
}
|
||||
|
||||
public void GiftRibbonsClear() => GiftRibbons.Clear();
|
||||
protected abstract int ExternalEventData { get; }
|
||||
private int ExternalEventData => LargeBlock.ExternalEventData;
|
||||
protected int ExternalEventFlags => ExternalEventData + 0x14;
|
||||
|
||||
public uint ColosseumRaw1
|
||||
|
|
|
|||
|
|
@ -6,13 +6,21 @@ namespace PKHeX.Core;
|
|||
/// <summary>
|
||||
/// Generation 3 <see cref="SaveFile"/> object for Pokémon Colosseum saves.
|
||||
/// </summary>
|
||||
public sealed class SAV3Colosseum : SaveFile, IGCSaveFile, IBoxDetailName, IDaycareStorage, IDaycareExperience, IGCRegion
|
||||
public sealed class SAV3Colosseum : SaveFile, IGCSaveFile, IBoxDetailName, IDaycareStorage, IDaycareExperience, IGCRegion, ISaveFileRevision
|
||||
{
|
||||
protected internal override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}";
|
||||
public override string Extension => this.GCExtension();
|
||||
public override PersonalTable3 Personal => PersonalTable.RS;
|
||||
public override ReadOnlySpan<ushort> HeldItems => Legal.HeldItems_RS;
|
||||
public SAV3GCMemoryCard? MemoryCard { get; init; }
|
||||
public int SaveRevision => 0;
|
||||
public string SaveRevisionString => OriginalRegion switch
|
||||
{
|
||||
GCRegion.NTSC_J => "-J",
|
||||
GCRegion.NTSC_U => "-U",
|
||||
GCRegion.PAL => "-PAL",
|
||||
_ => "-?",
|
||||
};
|
||||
|
||||
private readonly Memory<byte> Container;
|
||||
protected override Span<byte> BoxBuffer => Data;
|
||||
|
|
@ -256,7 +264,8 @@ public override int PlayedSeconds
|
|||
}
|
||||
|
||||
// Trainer Info (offset 0x78, length 0xB18, end @ 0xB90)
|
||||
public override string OT { get => GetString(Data.Slice(0x78, 20)); set { SetString(Data.Slice(0x78, 20), value, 10, StringConverterOption.ClearZero); OT2 = value; } }
|
||||
public Span<byte> OriginalTrainerTrash => Data.Slice(0x78, 20);
|
||||
public override string OT { get => GetString(OriginalTrainerTrash); set { SetString(OriginalTrainerTrash, value, 10, StringConverterOption.ClearZero); OT2 = value; } }
|
||||
public string OT2 { get => GetString(Data.Slice(0x8C, 20)); set => SetString(Data.Slice(0x8C, 20), value, 10, StringConverterOption.ClearZero); }
|
||||
|
||||
public override uint ID32 { get => ReadUInt32BigEndian(Data[0xA4..]); set => WriteUInt32BigEndian(Data[0xA4..], value); }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
|
@ -8,280 +7,70 @@ namespace PKHeX.Core;
|
|||
/// Generation 3 <see cref="SaveFile"/> object for <see cref="GameVersion.E"/>.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="SAV3" />
|
||||
public sealed class SAV3E : SAV3, IGen3Hoenn, IGen3Joyful, IGen3Wonder, IDaycareRandomState<uint>
|
||||
public sealed class SAV3E : SAV3, IDaycareRandomState<uint>
|
||||
{
|
||||
// Configuration
|
||||
protected override SAV3E CloneInternal() => new(GetFinalData()) { Language = Language };
|
||||
public override SaveBlock3SmallE SmallBlock { get; }
|
||||
public override SaveBlock3LargeE LargeBlock { get; }
|
||||
public override GameVersion Version { get => GameVersion.E; set { } }
|
||||
public override PersonalTable3 Personal => PersonalTable.E;
|
||||
|
||||
public override int EventFlagCount => 8 * 300;
|
||||
public override int EventWorkCount => 0x100;
|
||||
protected override int DaycareSlotSize => SIZE_STORED + 0x3C; // 0x38 mail + 4 exp
|
||||
protected override int EggEventFlag => 0x86;
|
||||
protected override int BadgeFlagStart => 0x867;
|
||||
|
||||
public SAV3E(Memory<byte> data) : base(data) => Initialize();
|
||||
public SAV3E(bool japanese = false) : base(japanese) => Initialize();
|
||||
public SAV3E(Memory<byte> data) : base(data)
|
||||
{
|
||||
SmallBlock = new SaveBlock3SmallE(SmallBuffer[..0xF2C]);
|
||||
LargeBlock = new SaveBlock3LargeE(LargeBuffer[..0x3D88]);
|
||||
}
|
||||
public SAV3E(bool japanese = false) : base(japanese)
|
||||
{
|
||||
SmallBlock = new SaveBlock3SmallE(SmallBuffer[..0xF2C]);
|
||||
LargeBlock = new SaveBlock3LargeE(LargeBuffer[..0x3D88]);
|
||||
}
|
||||
|
||||
public override PlayerBag3E Inventory => new(this);
|
||||
|
||||
protected override int EventFlag => 0x1270;
|
||||
protected override int EventWork => 0x139C;
|
||||
public override int MaxItemID => Legal.MaxItemID_3_E;
|
||||
|
||||
protected override int PokeDex => 0x18; // small
|
||||
protected override int DaycareOffset => 0x3030; // large
|
||||
|
||||
// storage
|
||||
private void Initialize() => Box = 0;
|
||||
|
||||
#region Small
|
||||
public override bool NationalDex
|
||||
{
|
||||
get => PokedexNationalMagicRSE == PokedexNationalUnlockRSE;
|
||||
get => SmallBlock.PokedexNationalMagicRSE == PokedexNationalUnlockRSE;
|
||||
set
|
||||
{
|
||||
PokedexMode = value ? (byte)1 : (byte)0; // mode
|
||||
PokedexNationalMagicRSE = value ? PokedexNationalUnlockRSE : (byte)0; // magic
|
||||
SmallBlock.PokedexMode = value ? (byte)1 : (byte)0; // mode
|
||||
SmallBlock.PokedexNationalMagicRSE = value ? PokedexNationalUnlockRSE : (byte)0; // magic
|
||||
SetEventFlag(0x896, value);
|
||||
SetWork(0x46, PokedexNationalUnlockWorkRSE);
|
||||
}
|
||||
}
|
||||
|
||||
public override uint SecurityKey
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Small[0xAC..]);
|
||||
set => WriteUInt32LittleEndian(Small[0xAC..], value);
|
||||
}
|
||||
|
||||
public RTC3 ClockInitial
|
||||
{
|
||||
get => new(Small.Slice(0x98, RTC3.Size).ToArray());
|
||||
set => SetData(Small[0x98..], value.Data);
|
||||
}
|
||||
|
||||
public RTC3 ClockElapsed
|
||||
{
|
||||
get => new(Small.Slice(0xA0, RTC3.Size).ToArray());
|
||||
set => SetData(Small[0xA0..], value.Data);
|
||||
}
|
||||
|
||||
public uint BerryPowder
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Small[0x1F4..]) ^ SecurityKey;
|
||||
set => WriteUInt32LittleEndian(Small[0x1F4..], value ^ SecurityKey);
|
||||
}
|
||||
|
||||
public ushort JoyfulJumpInRow { get => ReadUInt16LittleEndian(Small[0x1FC..]); set => WriteUInt16LittleEndian(Small[0x1FC..], Math.Min((ushort)9999, value)); }
|
||||
// u16 field2;
|
||||
public ushort JoyfulJump5InRow { get => ReadUInt16LittleEndian(Small[0x200..]); set => WriteUInt16LittleEndian(Small[0x200..], Math.Min((ushort)9999, value)); }
|
||||
public ushort JoyfulJumpGamesMaxPlayers { get => ReadUInt16LittleEndian(Small[0x202..]); set => WriteUInt16LittleEndian(Small[0x202..], Math.Min((ushort)9999, value)); }
|
||||
// u32 field8;
|
||||
public uint JoyfulJumpScore { get => ReadUInt16LittleEndian(Small[0x208..]); set => WriteUInt32LittleEndian(Small[0x208..], Math.Min(99990, value)); }
|
||||
|
||||
public uint JoyfulBerriesScore { get => ReadUInt16LittleEndian(Small[0x20C..]); set => WriteUInt32LittleEndian(Small[0x20C..], Math.Min(99990, value)); }
|
||||
public ushort JoyfulBerriesInRow { get => ReadUInt16LittleEndian(Small[0x210..]); set => WriteUInt16LittleEndian(Small[0x210..], Math.Min((ushort)9999, value)); }
|
||||
public ushort JoyfulBerries5InRow { get => ReadUInt16LittleEndian(Small[0x212..]); set => WriteUInt16LittleEndian(Small[0x212..], Math.Min((ushort)9999, value)); }
|
||||
|
||||
public BattleFrontier3 BattleFrontier => new(Small.Slice(0xCDC, BattleFrontier3.SIZE));
|
||||
|
||||
public uint BP
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Small[0xEB8..]);
|
||||
set
|
||||
{
|
||||
if (value > 9999)
|
||||
value = 9999;
|
||||
WriteUInt16LittleEndian(Small[0xEB8..], (ushort)value);
|
||||
}
|
||||
}
|
||||
|
||||
public uint BPEarned
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Small[0xEBA..]);
|
||||
set
|
||||
{
|
||||
if (value > 65535)
|
||||
value = 65535;
|
||||
WriteUInt16LittleEndian(Small[0xEBA..], (ushort)value);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Large
|
||||
public override int PartyCount { get => Large[0x234]; protected set => Large[0x234] = (byte)value; }
|
||||
public override int GetPartyOffset(int slot) => 0x238 + (SIZE_PARTY * slot);
|
||||
|
||||
public override uint Money
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Large[0x0490..]) ^ SecurityKey;
|
||||
set => WriteUInt32LittleEndian(Large[0x0490..], value ^ SecurityKey);
|
||||
get => LargeBlock.Money ^ SmallBlock.SecurityKey;
|
||||
set => LargeBlock.Money = value ^ SmallBlock.SecurityKey;
|
||||
}
|
||||
|
||||
public override uint Coin
|
||||
{
|
||||
get => (ushort)(ReadUInt16LittleEndian(Large[0x0494..]) ^ SecurityKey);
|
||||
set => WriteUInt16LittleEndian(Large[0x0494..], (ushort)(value ^ SecurityKey));
|
||||
get => (ushort)(LargeBlock.Coin ^ SmallBlock.SecurityKey);
|
||||
set => LargeBlock.Coin = (ushort)(value ^ SmallBlock.SecurityKey);
|
||||
}
|
||||
|
||||
private const int OFS_BerryBlenderRecord = 0x9BC;
|
||||
private const int OFS_TrendyWord = 0x2E20;
|
||||
private const int OFS_TrainerHillRecord = 0x3718;
|
||||
|
||||
private Span<byte> PokeBlockData => Large.Slice(0x848, PokeBlock3Case.SIZE);
|
||||
|
||||
public PokeBlock3Case PokeBlocks
|
||||
{
|
||||
get => new(PokeBlockData);
|
||||
set => value.Write(PokeBlockData);
|
||||
}
|
||||
|
||||
protected override int SeenOffset2 => 0x988;
|
||||
|
||||
public DecorationInventory3 Decorations => new(Large.Slice(0x2734, DecorationInventory3.SIZE));
|
||||
|
||||
private Span<byte> SwarmSpan => Large.Slice(0x2B90, Swarm3.SIZE);
|
||||
public Swarm3 Swarm
|
||||
{
|
||||
get => new(SwarmSpan.ToArray());
|
||||
set => SetData(SwarmSpan, value.Data);
|
||||
}
|
||||
|
||||
private void ClearSwarm() => SwarmSpan.Clear();
|
||||
|
||||
public IReadOnlyList<Swarm3> DefaultSwarms => Swarm3Details.Swarms_E;
|
||||
|
||||
public int SwarmIndex
|
||||
{
|
||||
get => Array.FindIndex(Swarm3Details.Swarms_E, z => z.MapNum == Swarm.MapNum);
|
||||
set
|
||||
{
|
||||
var arr = DefaultSwarms;
|
||||
if ((uint)value >= arr.Count)
|
||||
ClearSwarm();
|
||||
else
|
||||
Swarm = arr[value];
|
||||
}
|
||||
}
|
||||
|
||||
protected override int MailOffset => 0x2BE0;
|
||||
|
||||
protected override int GetDaycareEXPOffset(int slot) => GetDaycareSlotOffset(slot + 1) - 4; // @ end of each pk slot
|
||||
uint IDaycareRandomState<uint>.Seed // after the 2 slots, before the step counter
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Large[GetDaycareEXPOffset(2)..]);
|
||||
set => WriteUInt32LittleEndian(Large[GetDaycareEXPOffset(2)..], value);
|
||||
get => LargeBlock.DaycareSeed;
|
||||
set => LargeBlock.DaycareSeed = value;
|
||||
}
|
||||
|
||||
protected override int ExternalEventData => 0x31B3;
|
||||
|
||||
/// <summary>
|
||||
/// Max RPM for 2, 3 and 4 players. Each value unit represents 0.01 RPM. Value 0 if no record.
|
||||
/// </summary>
|
||||
/// <remarks>2 players: index 0, 3 players: index 1, 4 players: index 2</remarks>
|
||||
public const int BerryBlenderRPMRecordCount = 3;
|
||||
|
||||
private Span<byte> GetBlenderRPMSpan(int index)
|
||||
{
|
||||
if ((uint)index >= BerryBlenderRPMRecordCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
return Large[(OFS_BerryBlenderRecord + (index * 2))..];
|
||||
}
|
||||
|
||||
public ushort GetBerryBlenderRPMRecord(int index) => ReadUInt16LittleEndian(GetBlenderRPMSpan(index));
|
||||
|
||||
public void SetBerryBlenderRPMRecord(int index, ushort value)
|
||||
{
|
||||
WriteUInt16LittleEndian(GetBlenderRPMSpan(index), value);
|
||||
State.Edited = true;
|
||||
}
|
||||
|
||||
public bool GetTrendyWordUnlocked(TrendyWord3E word)
|
||||
{
|
||||
return GetFlag(OFS_TrendyWord + ((byte)word >> 3), (byte)word & 7);
|
||||
}
|
||||
|
||||
public void SetTrendyWordUnlocked(TrendyWord3E word, bool value)
|
||||
{
|
||||
SetFlag(OFS_TrendyWord + ((byte)word >> 3), (byte)word & 7, value);
|
||||
State.Edited = true;
|
||||
}
|
||||
|
||||
/** Each value unit represents 1/60th of a second. Value 0 if no record. */
|
||||
public uint GetTrainerHillRecord(TrainerHillMode3E mode)
|
||||
{
|
||||
return ReadUInt32LittleEndian(Large[(OFS_TrainerHillRecord + ((byte)mode * 4))..]);
|
||||
}
|
||||
|
||||
public void SetTrainerHillRecord(TrainerHillMode3E mode, uint value)
|
||||
{
|
||||
WriteUInt32LittleEndian(Large[(OFS_TrainerHillRecord + ((byte)mode * 4))..], value);
|
||||
State.Edited = true;
|
||||
}
|
||||
|
||||
#region eBerry
|
||||
private const int OFFSET_EBERRY = 0x31F8;
|
||||
private const int SIZE_EBERRY = 0x34;
|
||||
|
||||
public override Span<byte> EReaderBerry() => Large.Slice(OFFSET_EBERRY, SIZE_EBERRY);
|
||||
#endregion
|
||||
|
||||
#region eTrainer
|
||||
public override Span<byte> EReaderTrainer() => Small.Slice(0xBEC, 0xBC);
|
||||
#endregion
|
||||
|
||||
public int WonderOffset => WonderNewsOffset;
|
||||
private const int WonderNewsOffset = 0x322C;
|
||||
private int WonderCardOffset => WonderNewsOffset + (Japanese ? WonderNews3.SIZE_JAP : WonderNews3.SIZE);
|
||||
private int WonderCardExtraOffset => WonderCardOffset + (Japanese ? WonderCard3.SIZE_JAP : WonderCard3.SIZE);
|
||||
|
||||
private Span<byte> WonderNewsData => Large.Slice(WonderNewsOffset, Japanese ? WonderNews3.SIZE_JAP : WonderNews3.SIZE);
|
||||
private Span<byte> WonderCardData => Large.Slice(WonderCardOffset, Japanese ? WonderCard3.SIZE_JAP : WonderCard3.SIZE);
|
||||
private Span<byte> WonderCardExtraData => Large.Slice(WonderCardExtraOffset, WonderCard3Extra.SIZE);
|
||||
|
||||
public WonderNews3 WonderNews { get => new(WonderNewsData.ToArray()); set => SetData(WonderNewsData, value.Data); }
|
||||
public WonderCard3 WonderCard { get => new(WonderCardData.ToArray()); set => SetData(WonderCardData, value.Data); }
|
||||
public WonderCard3Extra WonderCardExtra { get => new(WonderCardExtraData.ToArray()); set => SetData(WonderCardExtraData, value.Data); }
|
||||
// 0x338: 4 easy chat words
|
||||
// 0x340: news MENewsJisanStruct
|
||||
// 0x344: uint[5], uint[5] tracking?
|
||||
|
||||
private Span<byte> MysterySpan => Large.Slice(0x3728, MysteryEvent3.SIZE);
|
||||
public override Gen3MysteryData MysteryData
|
||||
{
|
||||
get => new MysteryEvent3(MysterySpan.ToArray());
|
||||
set => SetData(MysterySpan, value.Data);
|
||||
}
|
||||
|
||||
private Span<byte> RecordMixingData => Large.Slice(0x3B14, RecordMixing3Gift.SIZE);
|
||||
public RecordMixing3Gift RecordMixingGift { get => new(RecordMixingData.ToArray()); set => SetData(RecordMixingData, value.Data); }
|
||||
|
||||
protected override int SeenOffset3 => 0x3B24;
|
||||
|
||||
private const int Walda = 0x3D70;
|
||||
public ushort WaldaBackgroundColor { get => ReadUInt16LittleEndian(Large[(Walda + 0)..]); set => WriteUInt16LittleEndian(Large[(Walda + 0)..], value); }
|
||||
public ushort WaldaForegroundColor { get => ReadUInt16LittleEndian(Large[(Walda + 2)..]); set => WriteUInt16LittleEndian(Large[(Walda + 2)..], value); }
|
||||
public byte WaldaIconID { get => Large[Walda + 0x14]; set => Large[Walda + 0x14] = value; }
|
||||
public byte WaldaPatternID { get => Large[Walda + 0x15]; set => Large[Walda + 0x15] = value; }
|
||||
public bool WaldaUnlocked { get => Large[Walda + 0x16] != 0; set => Large[Walda + 0x16] = (byte)(value ? 1 : 0); }
|
||||
|
||||
private Memory<byte> SecretBaseData => LargeBuffer.Slice(0x1A9C, SecretBaseManager3.BaseCount * SecretBase3.SIZE);
|
||||
public SecretBaseManager3 SecretBases => new(SecretBaseData);
|
||||
|
||||
private const int Painting = 0x2F90;
|
||||
private const int CountPaintings = 5;
|
||||
private Span<byte> GetPaintingSpan(int index)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, CountPaintings);
|
||||
return Large.Slice(Painting + (Paintings3.SIZE * index), Paintings3.SIZE * CountPaintings);
|
||||
}
|
||||
public Paintings3 GetPainting(int index) => new(GetPaintingSpan(index).ToArray(), Japanese);
|
||||
public void SetPainting(int index, Paintings3 value) => value.Data.CopyTo(GetPaintingSpan(index));
|
||||
#endregion
|
||||
|
||||
private const uint EXTRADATA_SENTINEL = 0x0000B39D;
|
||||
public bool HasBattleVideo => Data.Length > SaveUtil.SIZE_G3RAWHALF && ReadUInt32LittleEndian(GetFinalExternalData().Span) == EXTRADATA_SENTINEL;
|
||||
public bool HasBattleVideo => IsFullSaveFile && ReadUInt32LittleEndian(GetFinalExternalData().Span) == EXTRADATA_SENTINEL;
|
||||
|
||||
public void SetExtraDataSentinelBattleVideo() => WriteUInt32LittleEndian(GetFinalExternalData().Span, EXTRADATA_SENTINEL);
|
||||
|
||||
public Memory<byte> BattleVideoData => GetFinalExternalData().Slice(4, BattleVideo3.SIZE);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
|
|
@ -7,41 +6,39 @@ namespace PKHeX.Core;
|
|||
/// Generation 5 <see cref="SaveFile"/> object for <see cref="GameVersion.FRLG"/>.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="SAV3" />
|
||||
public sealed class SAV3FRLG : SAV3, IGen3Joyful, IGen3Wonder, IDaycareRandomState<ushort>
|
||||
public sealed class SAV3FRLG : SAV3, IDaycareRandomState<ushort>
|
||||
{
|
||||
// Configuration
|
||||
protected override SAV3FRLG CloneInternal() => new(GetFinalData()) { Language = Language };
|
||||
public override GameVersion Version { get; set; } = GameVersion.FR; // allow mutation
|
||||
public override SaveBlock3SmallFRLG SmallBlock { get; }
|
||||
public override SaveBlock3LargeFRLG LargeBlock { get; }
|
||||
public override GameVersion Version
|
||||
{
|
||||
get;
|
||||
set => field = value is GameVersion.FR or GameVersion.LG ? value : GameVersion.FRLG;
|
||||
} = GameVersion.FR; // allow mutation
|
||||
private PersonalTable3 _personal = PersonalTable.FR;
|
||||
public override PersonalTable3 Personal => _personal;
|
||||
public override int MaxItemID => Legal.MaxItemID_3_FRLG;
|
||||
|
||||
public override int EventFlagCount => 8 * 288;
|
||||
public override int EventWorkCount => 0x100;
|
||||
protected override int DaycareSlotSize => SIZE_STORED + 0x3C; // 0x38 mail + 4 exp
|
||||
protected override int EggEventFlag => 0x266;
|
||||
protected override int BadgeFlagStart => 0x820;
|
||||
|
||||
public SAV3FRLG(bool japanese = false) : base(japanese) => Initialize();
|
||||
|
||||
public override PlayerBag3FRLG Inventory => new(this);
|
||||
public SAV3FRLG(bool japanese = false) : base(japanese)
|
||||
{
|
||||
SmallBlock = new SaveBlock3SmallFRLG(SmallBuffer[..0xF24]);
|
||||
LargeBlock = new SaveBlock3LargeFRLG(LargeBuffer[..0x3D68]);
|
||||
}
|
||||
|
||||
public SAV3FRLG(Memory<byte> data) : base(data)
|
||||
{
|
||||
Initialize();
|
||||
SmallBlock = new SaveBlock3SmallFRLG(SmallBuffer[..0xF24]);
|
||||
LargeBlock = new SaveBlock3LargeFRLG(LargeBuffer[..0x3D68]);
|
||||
|
||||
// Fix save files that have an overflow corruption with the Pokédex.
|
||||
// Future loads of this save file will cause it to be recognized as FR/LG correctly.
|
||||
if (IsCorruptPokedexFF())
|
||||
WriteUInt32LittleEndian(Small[0xAC..], 1);
|
||||
SmallBlock.FixDummyFlags();
|
||||
}
|
||||
|
||||
protected override int EventFlag => 0xEE0;
|
||||
protected override int EventWork => 0x1000;
|
||||
protected override int PokeDex => 0x18; // small
|
||||
protected override int DaycareOffset => 0x2F80; // large
|
||||
|
||||
// storage
|
||||
private void Initialize() => Box = 0;
|
||||
public override PlayerBag3FRLG Inventory => new(this);
|
||||
|
||||
public bool ResetPersonal(GameVersion g)
|
||||
{
|
||||
|
|
@ -55,105 +52,40 @@ public bool ResetPersonal(GameVersion g)
|
|||
#region Small
|
||||
public override bool NationalDex
|
||||
{
|
||||
get => PokedexNationalMagicFRLG == PokedexNationalUnlockFRLG;
|
||||
get => SmallBlock.PokedexNationalMagicFRLG == PokedexNationalUnlockFRLG;
|
||||
set
|
||||
{
|
||||
PokedexNationalMagicFRLG = value ? PokedexNationalUnlockFRLG : (byte)0; // magic
|
||||
SmallBlock.PokedexNationalMagicFRLG = value ? PokedexNationalUnlockFRLG : (byte)0;
|
||||
SetEventFlag(0x840, value);
|
||||
SetWork(0x4E, PokedexNationalUnlockWorkFRLG);
|
||||
}
|
||||
}
|
||||
|
||||
public uint BerryPowder
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Small[0xAF8..]) ^ SecurityKey;
|
||||
set => WriteUInt32LittleEndian(Small[0xAF8..], value ^ SecurityKey);
|
||||
}
|
||||
|
||||
public ushort JoyfulJumpInRow { get => ReadUInt16LittleEndian(Small[0xB00..]); set => WriteUInt16LittleEndian(Small[0xB00..], Math.Min((ushort)9999, value)); }
|
||||
// u16 field2;
|
||||
public ushort JoyfulJump5InRow { get => ReadUInt16LittleEndian(Small[0xB04..]); set => WriteUInt16LittleEndian(Small[0xB04..], Math.Min((ushort)9999, value)); }
|
||||
public ushort JoyfulJumpGamesMaxPlayers { get => ReadUInt16LittleEndian(Small[0xB06..]); set => WriteUInt16LittleEndian(Small[0xB06..], Math.Min((ushort)9999, value)); }
|
||||
// u32 field8;
|
||||
public uint JoyfulJumpScore { get => ReadUInt16LittleEndian(Small[0xB0C..]); set => WriteUInt32LittleEndian(Small[0xB0C..], Math.Min(99990, value)); }
|
||||
|
||||
public uint JoyfulBerriesScore { get => ReadUInt16LittleEndian(Small[0xB10..]); set => WriteUInt32LittleEndian(Small[0xB10..], Math.Min(99990, value)); }
|
||||
public ushort JoyfulBerriesInRow { get => ReadUInt16LittleEndian(Small[0xB14..]); set => WriteUInt16LittleEndian(Small[0xB14..], Math.Min((ushort)9999, value)); }
|
||||
public ushort JoyfulBerries5InRow { get => ReadUInt16LittleEndian(Small[0xB16..]); set => WriteUInt16LittleEndian(Small[0xB16..], Math.Min((ushort)9999, value)); }
|
||||
|
||||
public override uint SecurityKey
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Small[0xF20..]);
|
||||
set => WriteUInt32LittleEndian(Small[0xF20..], value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Large
|
||||
public override int PartyCount { get => Large[0x034]; protected set => Large[0x034] = (byte)value; }
|
||||
public override int GetPartyOffset(int slot) => 0x038 + (SIZE_PARTY * slot);
|
||||
|
||||
public override uint Money
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Large[0x0290..]) ^ SecurityKey;
|
||||
set => WriteUInt32LittleEndian(Large[0x0290..], value ^ SecurityKey);
|
||||
get => LargeBlock.Money ^ SmallBlock.SecurityKey;
|
||||
set => LargeBlock.Money = value ^ SmallBlock.SecurityKey;
|
||||
}
|
||||
|
||||
public override uint Coin
|
||||
{
|
||||
get => (ushort)(ReadUInt16LittleEndian(Large[0x0294..]) ^ SecurityKey);
|
||||
set => WriteUInt16LittleEndian(Large[0x0294..], (ushort)(value ^ SecurityKey));
|
||||
get => (ushort)(LargeBlock.Coin ^ SmallBlock.SecurityKey);
|
||||
set => LargeBlock.Coin = (ushort)(value ^ SmallBlock.SecurityKey);
|
||||
}
|
||||
|
||||
protected override int SeenOffset2 => 0x5F8;
|
||||
protected override int MailOffset => 0x2CD0;
|
||||
|
||||
protected override int GetDaycareEXPOffset(int slot) => GetDaycareSlotOffset(slot + 1) - 4; // @ end of each pk slot
|
||||
ushort IDaycareRandomState<ushort>.Seed
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Large[GetDaycareEXPOffset(2)..]);
|
||||
set => WriteUInt16LittleEndian(Large[GetDaycareEXPOffset(2)..], value);
|
||||
get => LargeBlock.DaycareSeed;
|
||||
set => LargeBlock.DaycareSeed = value;
|
||||
}
|
||||
|
||||
protected override int ExternalEventData => 0x30A7;
|
||||
|
||||
#region eBerry
|
||||
private const int OFFSET_EBERRY = 0x30EC;
|
||||
private const int SIZE_EBERRY = 0x34;
|
||||
|
||||
public override Span<byte> EReaderBerry() => Large.Slice(OFFSET_EBERRY, SIZE_EBERRY);
|
||||
#endregion
|
||||
|
||||
#region eTrainer
|
||||
public override Span<byte> EReaderTrainer() => Small.Slice(0x4A0, 0xBC);
|
||||
#endregion
|
||||
|
||||
public int WonderOffset => WonderNewsOffset;
|
||||
private const int WonderNewsOffset = 0x3120;
|
||||
private int WonderCardOffset => WonderNewsOffset + (Japanese ? WonderNews3.SIZE_JAP : WonderNews3.SIZE);
|
||||
private int WonderCardExtraOffset => WonderCardOffset + (Japanese ? WonderCard3.SIZE_JAP : WonderCard3.SIZE);
|
||||
|
||||
private Span<byte> WonderNewsData => Large.Slice(WonderNewsOffset, Japanese ? WonderNews3.SIZE_JAP : WonderNews3.SIZE);
|
||||
private Span<byte> WonderCardData => Large.Slice(WonderCardOffset, Japanese ? WonderCard3.SIZE_JAP : WonderCard3.SIZE);
|
||||
private Span<byte> WonderCardExtraData => Large.Slice(WonderCardExtraOffset, WonderCard3Extra.SIZE);
|
||||
|
||||
public WonderNews3 WonderNews { get => new(WonderNewsData.ToArray()); set => SetData(WonderNewsData, value.Data); }
|
||||
public WonderCard3 WonderCard { get => new(WonderCardData.ToArray()); set => SetData(WonderCardData, value.Data); }
|
||||
public WonderCard3Extra WonderCardExtra { get => new(WonderCardExtraData.ToArray()); set => SetData(WonderCardExtraData, value.Data); }
|
||||
|
||||
// 0x338: 4 easy chat words
|
||||
// 0x340: news MENewsJisanStruct
|
||||
// 0x344: uint[5], uint[5] tracking?
|
||||
|
||||
private Span<byte> MysterySpan => Large.Slice(0x361C, MysteryEvent3.SIZE);
|
||||
public override Gen3MysteryData MysteryData { get => new MysteryEvent3(MysterySpan.ToArray()); set => SetData(MysterySpan, value.Data); }
|
||||
|
||||
protected override int SeenOffset3 => 0x3A18;
|
||||
|
||||
public string RivalName
|
||||
{
|
||||
get => GetString(Large.Slice(0x3A4C, 8));
|
||||
set => SetString(Large.Slice(0x3A4C, 8), value, 7, StringConverterOption.ClearZero);
|
||||
get => GetString(LargeBlock.RivalNameTrash);
|
||||
set => SetString(LargeBlock.RivalNameTrash, value, 7, StringConverterOption.ClearZero);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
|
|
@ -8,160 +6,68 @@ namespace PKHeX.Core;
|
|||
/// Generation 3 <see cref="SaveFile"/> object for <see cref="GameVersion.RS"/>.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="SAV3" />
|
||||
public sealed class SAV3RS : SAV3, IGen3Hoenn, IDaycareRandomState<ushort>
|
||||
public sealed class SAV3RS : SAV3, IDaycareRandomState<ushort>
|
||||
{
|
||||
// Configuration
|
||||
protected override SAV3RS CloneInternal() => new(GetFinalData()) { Language = Language };
|
||||
public override SaveBlock3SmallRS SmallBlock { get; }
|
||||
public override SaveBlock3LargeRS LargeBlock { get; }
|
||||
|
||||
public override GameVersion Version
|
||||
{
|
||||
get;
|
||||
set => field = value is GameVersion.RS or GameVersion.R or GameVersion.S ? value : GameVersion.RS;
|
||||
set => field = value is GameVersion.R or GameVersion.S ? value : GameVersion.RS;
|
||||
} = GameVersion.RS;
|
||||
|
||||
public override PersonalTable3 Personal => PersonalTable.RS;
|
||||
public override int MaxItemID => Legal.MaxItemID_3_RS;
|
||||
|
||||
public override int EventFlagCount => 8 * 288;
|
||||
public override int EventWorkCount => 0x100;
|
||||
protected override int DaycareSlotSize => SIZE_STORED;
|
||||
protected override int EggEventFlag => 0x86;
|
||||
protected override int BadgeFlagStart => 0x807;
|
||||
public SAV3RS(Memory<byte> data) : base(data)
|
||||
{
|
||||
SmallBlock = new SaveBlock3SmallRS(SmallBuffer[..0x890]);
|
||||
LargeBlock = new SaveBlock3LargeRS(LargeBuffer[..0x3AC0]);
|
||||
}
|
||||
|
||||
public SAV3RS(Memory<byte> data) : base(data) => Initialize();
|
||||
public SAV3RS(bool japanese = false) : base(japanese) => Initialize();
|
||||
public SAV3RS(bool japanese = false) : base(japanese)
|
||||
{
|
||||
SmallBlock = new SaveBlock3SmallRS(SmallBuffer[..0x890]);
|
||||
LargeBlock = new SaveBlock3LargeRS(LargeBuffer[..0x3AC0]);
|
||||
}
|
||||
|
||||
public override PlayerBag3RS Inventory => new(this);
|
||||
|
||||
protected override int EventFlag => 0x1220;
|
||||
protected override int EventWork => 0x1340;
|
||||
|
||||
protected override int PokeDex => 0x18; // small
|
||||
protected override int DaycareOffset => 0x2F9C; // large
|
||||
|
||||
// storage
|
||||
private void Initialize() => Box = 0;
|
||||
|
||||
#region Small
|
||||
public override bool NationalDex
|
||||
{
|
||||
get => PokedexNationalMagicRSE == PokedexNationalUnlockRSE;
|
||||
get => SmallBlock.PokedexNationalMagicRSE == PokedexNationalUnlockRSE;
|
||||
set
|
||||
{
|
||||
PokedexMode = value ? (byte)1 : (byte)0; // mode
|
||||
PokedexNationalMagicRSE = value ? PokedexNationalUnlockRSE : (byte)0; // magic
|
||||
SmallBlock.PokedexMode = value ? (byte)1 : (byte)0;
|
||||
SmallBlock.PokedexNationalMagicRSE = value ? PokedexNationalUnlockRSE : (byte)0;
|
||||
SetEventFlag(0x836, value);
|
||||
SetWork(0x46, PokedexNationalUnlockWorkRSE);
|
||||
}
|
||||
}
|
||||
|
||||
public override uint SecurityKey { get => 0; set { } }
|
||||
|
||||
public RTC3 ClockInitial
|
||||
{
|
||||
get => new(Small.Slice(0x98, RTC3.Size).ToArray());
|
||||
set => SetData(Small.Slice(0x98, RTC3.Size), value.Data);
|
||||
}
|
||||
|
||||
public RTC3 ClockElapsed
|
||||
{
|
||||
get => new(Small.Slice(0xA0, RTC3.Size).ToArray());
|
||||
set => SetData(Small.Slice(0xA0, RTC3.Size), value.Data);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Large
|
||||
public override int PartyCount { get => Large[0x234]; protected set => Large[0x234] = (byte)value; }
|
||||
public override int GetPartyOffset(int slot) => 0x238 + (SIZE_PARTY * slot);
|
||||
|
||||
public override uint Money
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Large[0x0490..]);
|
||||
set => WriteUInt32LittleEndian(Large[0x0490..], value);
|
||||
get => LargeBlock.Money;
|
||||
set => LargeBlock.Money = value;
|
||||
}
|
||||
|
||||
public override uint Coin
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Large[0x0494..]);
|
||||
set => WriteUInt16LittleEndian(Large[0x0494..], (ushort)(value));
|
||||
get => LargeBlock.Coin;
|
||||
set => LargeBlock.Coin = (ushort)value;
|
||||
}
|
||||
|
||||
private Span<byte> PokeBlockData => Large.Slice(0x7F8, PokeBlock3Case.SIZE);
|
||||
|
||||
public PokeBlock3Case PokeBlocks
|
||||
{
|
||||
get => new(PokeBlockData);
|
||||
set => value.Write(PokeBlockData);
|
||||
}
|
||||
|
||||
protected override int SeenOffset2 => 0x938;
|
||||
|
||||
public DecorationInventory3 Decorations => new(Large.Slice(0x26A0, DecorationInventory3.SIZE));
|
||||
|
||||
private Span<byte> SwarmData => Large.Slice(0x2AFC, Swarm3.SIZE);
|
||||
public Swarm3 Swarm
|
||||
{
|
||||
get => new(SwarmData.ToArray());
|
||||
set => SetData(SwarmData, value.Data);
|
||||
}
|
||||
|
||||
private void ClearSwarm() => Large.Slice(0x2AFC, Swarm3.SIZE).Clear();
|
||||
|
||||
public IReadOnlyList<Swarm3> DefaultSwarms => Swarm3Details.Swarms_RS;
|
||||
|
||||
public int SwarmIndex
|
||||
{
|
||||
get => Array.FindIndex(Swarm3Details.Swarms_RS, z => z.MapNum == Swarm.MapNum);
|
||||
set
|
||||
{
|
||||
var arr = DefaultSwarms;
|
||||
if ((uint)value >= arr.Count)
|
||||
ClearSwarm();
|
||||
else
|
||||
Swarm = arr[value];
|
||||
}
|
||||
}
|
||||
|
||||
protected override int MailOffset => 0x2B4C;
|
||||
|
||||
protected override int GetDaycareEXPOffset(int slot) => GetDaycareSlotOffset(2) + (2 * 0x38) + (4 * slot); // consecutive vals, after both consecutive slots & 2 mail
|
||||
ushort IDaycareRandomState<ushort>.Seed
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Large[GetDaycareEXPOffset(2)..]);
|
||||
set => WriteUInt16LittleEndian(Large[GetDaycareEXPOffset(2)..], value);
|
||||
get => LargeBlock.DaycareSeed;
|
||||
set => LargeBlock.DaycareSeed = value;
|
||||
}
|
||||
|
||||
protected override int ExternalEventData => 0x311B;
|
||||
|
||||
#region eBerry
|
||||
private const int OFFSET_EBERRY = 0x3160;
|
||||
private const int SIZE_EBERRY = 0x530;
|
||||
|
||||
public override Span<byte> EReaderBerry() => Large.Slice(OFFSET_EBERRY, SIZE_EBERRY);
|
||||
#endregion
|
||||
|
||||
#region eTrainer
|
||||
public override Span<byte> EReaderTrainer() => Small.Slice(0x498, 0xBC);
|
||||
#endregion
|
||||
|
||||
private Span<byte> MysterySpan => Large.Slice(0x3690, MysteryEvent3.SIZE);
|
||||
public override Gen3MysteryData MysteryData { get => new MysteryEvent3(MysterySpan.ToArray()); set => SetData(MysterySpan, value.Data); }
|
||||
|
||||
private Span<byte> RecordSpan => Large.Slice(0x3A7C, RecordMixing3Gift.SIZE);
|
||||
public RecordMixing3Gift RecordMixingGift { get => new(RecordSpan.ToArray()); set => SetData(RecordSpan, value.Data); }
|
||||
|
||||
protected override int SeenOffset3 => 0x3A8C;
|
||||
|
||||
private Memory<byte> SecretBaseData => LargeBuffer.Slice(0x1A08, SecretBaseManager3.BaseCount * SecretBase3.SIZE);
|
||||
public SecretBaseManager3 SecretBases => new(SecretBaseData);
|
||||
|
||||
private const int Painting = 0x2EFC;
|
||||
private const int CountPaintings = 5;
|
||||
private Span<byte> GetPaintingSpan(int index)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, CountPaintings);
|
||||
return Large.Slice(Painting + (Paintings3.SIZE * index), Paintings3.SIZE * CountPaintings);
|
||||
}
|
||||
public Paintings3 GetPainting(int index) => new(GetPaintingSpan(index).ToArray(), Japanese);
|
||||
public void SetPainting(int index, Paintings3 value) => value.Data.CopyTo(GetPaintingSpan(index));
|
||||
#endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ public override void CopyChangesFrom(SaveFile sav)
|
|||
public override ushort MaxMoveID => Legal.MaxMoveID_3;
|
||||
public override ushort MaxSpeciesID => Legal.MaxSpeciesID_3;
|
||||
public override int MaxAbilityID => Legal.MaxAbilityID_3;
|
||||
public override int MaxItemID => Legal.MaxItemID_3;
|
||||
public override int MaxItemID => Legal.MaxItemID_3_RS;
|
||||
public override int MaxBallID => Legal.MaxBallID_3;
|
||||
public override GameVersion MaxGameID => Legal.MaxGameID_3;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,9 +7,18 @@ namespace PKHeX.Core;
|
|||
/// <summary>
|
||||
/// Generation 3 <see cref="SaveFile"/> object for Pokémon XD saves.
|
||||
/// </summary>
|
||||
public sealed class SAV3XD : SaveFile, IGCSaveFile, IBoxDetailName, IDaycareStorage, IDaycareExperience, IGCRegion
|
||||
public sealed class SAV3XD : SaveFile, IGCSaveFile, IBoxDetailName, IDaycareStorage, IDaycareExperience, IGCRegion, ISaveFileRevision
|
||||
{
|
||||
protected internal override string ShortSummary => $"{OT} ({Version}) {PlayTimeString}";
|
||||
public int SaveRevision => 0;
|
||||
public string SaveRevisionString => OriginalRegion switch
|
||||
{
|
||||
GCRegion.NTSC_J => "-J",
|
||||
GCRegion.NTSC_U => "-U",
|
||||
GCRegion.PAL => "-PAL",
|
||||
_ => "-?",
|
||||
};
|
||||
|
||||
public override string Extension => this.GCExtension();
|
||||
public SAV3GCMemoryCard? MemoryCard { get; init; }
|
||||
|
||||
|
|
@ -234,7 +243,8 @@ public override int PlayedSeconds
|
|||
|
||||
// Trainer Info
|
||||
public override GameVersion Version { get => GameVersion.XD; set { } }
|
||||
public override string OT { get => GetString(Data.Slice(Trainer1 + 0x00, 20)); set => SetString(Data.Slice(Trainer1 + 0x00, 20), value, 10, StringConverterOption.ClearZero); }
|
||||
public Span<byte> OriginalTrainerTrash => Data.Slice(Trainer1 + 0x00, 20);
|
||||
public override string OT { get => GetString(OriginalTrainerTrash); set => SetString(OriginalTrainerTrash, value, 10, StringConverterOption.ClearZero); }
|
||||
public override uint ID32 { get => ReadUInt32BigEndian(Data[(Trainer1 + 0x2C)..]); set => WriteUInt32BigEndian(Data[(Trainer1 + 0x2C)..], value); }
|
||||
public override ushort SID16 { get => ReadUInt16BigEndian(Data[(Trainer1 + 0x2C)..]); set => WriteUInt16BigEndian(Data[(Trainer1 + 0x2C)..], value); }
|
||||
public override ushort TID16 { get => ReadUInt16BigEndian(Data[(Trainer1 + 0x2E)..]); set => WriteUInt16BigEndian(Data[(Trainer1 + 0x2E)..], value); }
|
||||
|
|
|
|||
|
|
@ -238,10 +238,10 @@ private int GetActiveExtraBlock(BlockInfo4 block)
|
|||
private int OFS_Backdrop => FashionCase + 0x28;
|
||||
|
||||
protected int OFS_Chatter = int.MinValue;
|
||||
public Chatter4 Chatter => new(this, Buffer[OFS_Chatter..]);
|
||||
public Chatter4 Chatter => new(this, GeneralBuffer[OFS_Chatter..]);
|
||||
|
||||
protected int OFS_Record = int.MinValue;
|
||||
public Record4 Records => new(this, Buffer.Slice(OFS_Record, Record4.GetSize(this)));
|
||||
public Record4 Records => new(this, GeneralBuffer.Slice(OFS_Record, Record4.GetSize(this)));
|
||||
|
||||
protected int OFS_Groups = int.MinValue;
|
||||
|
||||
|
|
@ -255,10 +255,13 @@ public override int PartyCount
|
|||
public sealed override int GetPartyOffset(int slot) => Party + (SIZE_PARTY * slot);
|
||||
|
||||
#region Trainer Info
|
||||
|
||||
public Span<byte> OriginalTrainerTrash => General.Slice(Trainer1, 16);
|
||||
|
||||
public override string OT
|
||||
{
|
||||
get => GetString(General.Slice(Trainer1, 16));
|
||||
set => SetString(General.Slice(Trainer1, 16), value, MaxStringLengthTrainer, StringConverterOption.ClearZero);
|
||||
get => GetString(OriginalTrainerTrash);
|
||||
set => SetString(OriginalTrainerTrash, value, MaxStringLengthTrainer, StringConverterOption.ClearZero);
|
||||
}
|
||||
|
||||
public override uint ID32
|
||||
|
|
@ -337,13 +340,13 @@ public override int PlayedSeconds
|
|||
public abstract int X { get; set; }
|
||||
public abstract int Y { get; set; }
|
||||
|
||||
public string Rival
|
||||
public string RivalName
|
||||
{
|
||||
get => GetString(RivalTrash);
|
||||
set => SetString(RivalTrash, value, MaxStringLengthTrainer, StringConverterOption.ClearZero);
|
||||
get => GetString(RivalNameTrash);
|
||||
set => SetString(RivalNameTrash, value, MaxStringLengthTrainer, StringConverterOption.ClearZero);
|
||||
}
|
||||
|
||||
public abstract Span<byte> RivalTrash { get; set; }
|
||||
public abstract Span<byte> RivalNameTrash { get; set; }
|
||||
|
||||
public abstract int X2 { get; set; }
|
||||
public abstract int Y2 { get; set; }
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ public override void SetBoxWallpaper(int box, int value)
|
|||
public override int X { get => ReadUInt16LittleEndian(General[0x1240..]); set => WriteUInt16LittleEndian(General[0x1240..], (ushort)(X2 = value)); }
|
||||
public override int Y { get => ReadUInt16LittleEndian(General[0x1244..]); set => WriteUInt16LittleEndian(General[0x1244..], (ushort)(Y2 = value)); }
|
||||
|
||||
public override Span<byte> RivalTrash
|
||||
public override Span<byte> RivalNameTrash
|
||||
{
|
||||
get => General.Slice(0x25A8, MaxStringLengthTrainer * 2);
|
||||
set { if (value.Length == MaxStringLengthTrainer * 2) value.CopyTo(General[0x25A8..]); }
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ protected override void SetPKM(PKM pk, bool isParty = false)
|
|||
public override int X { get => ReadUInt16LittleEndian(General[0x123C..]); set => WriteUInt16LittleEndian(General[0x123C..], (ushort)(X2 = value)); }
|
||||
public override int Y { get => ReadUInt16LittleEndian(General[0x1240..]); set => WriteUInt16LittleEndian(General[0x1240..], (ushort)(Y2 = value)); }
|
||||
|
||||
public override Span<byte> RivalTrash
|
||||
public override Span<byte> RivalNameTrash
|
||||
{
|
||||
get => RivalSpan;
|
||||
set { if (value.Length == MaxStringLengthTrainer * 2) value.CopyTo(RivalSpan); }
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ public void SetWallpaperUnlocked(Wallpaper4Pt wallpaperId, bool value)
|
|||
public override int X { get => ReadUInt16LittleEndian(General[0x1288..]); set => WriteUInt16LittleEndian(General[0x1288..], (ushort)(X2 = value)); }
|
||||
public override int Y { get => ReadUInt16LittleEndian(General[0x128C..]); set => WriteUInt16LittleEndian(General[0x128C..], (ushort)(Y2 = value)); }
|
||||
|
||||
public override Span<byte> RivalTrash
|
||||
public override Span<byte> RivalNameTrash
|
||||
{
|
||||
get => RivalSpan;
|
||||
set { if (value.Length == MaxStringLengthTrainer * 2) value.CopyTo(RivalSpan); }
|
||||
|
|
|
|||
|
|
@ -51,13 +51,13 @@ public sealed class SAV5B2W2 : SAV5, ISaveBlock5B2W2
|
|||
public MedalList5 Medals => Blocks.Medals;
|
||||
public KeySystem5 Keys => Blocks.Keys;
|
||||
|
||||
public string Rival
|
||||
public string RivalName
|
||||
{
|
||||
get => GetString(RivalTrash);
|
||||
set => SetString(RivalTrash, value, MaxStringLengthTrainer, StringConverterOption.ClearZero);
|
||||
get => GetString(RivalNameTrash);
|
||||
set => SetString(RivalNameTrash, value, MaxStringLengthTrainer, StringConverterOption.ClearZero);
|
||||
}
|
||||
|
||||
public Span<byte> RivalTrash
|
||||
public Span<byte> RivalNameTrash
|
||||
{
|
||||
get => Data.Slice(0x23BA4, MaxStringLengthTrainer * 2);
|
||||
set { if (value.Length == MaxStringLengthTrainer * 2) value.CopyTo(Data[0x23BA4..]); }
|
||||
|
|
|
|||
|
|
@ -280,10 +280,12 @@ public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, i
|
|||
public override int CurrentBox { get => BoxLayout.CurrentBox; set => BoxLayout.CurrentBox = (byte)value; }
|
||||
public override int BoxesUnlocked { get => BoxLayout.BoxesUnlocked; set => BoxLayout.BoxesUnlocked = (byte)value; }
|
||||
|
||||
public string Rival
|
||||
public Span<byte> RivalNameTrash => Data.Slice(0x55F4, 0x1A);
|
||||
|
||||
public string RivalName
|
||||
{
|
||||
get => GetString(Data.Slice(0x55F4, 0x1A));
|
||||
set => SetString(Data.Slice(0x55F4, 0x1A), value, MaxStringLengthTrainer, StringConverterOption.ClearZero);
|
||||
get => GetString(RivalNameTrash);
|
||||
set => SetString(RivalNameTrash, value, MaxStringLengthTrainer, StringConverterOption.ClearZero);
|
||||
}
|
||||
|
||||
public short ZoneID // map
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ public SAV8LA()
|
|||
{
|
||||
0 => "-Base", // Vanilla
|
||||
1 => "-DB", // DLC 1: Daybreak
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(SaveRevision)),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(SaveRevision), SaveRevision, null),
|
||||
};
|
||||
|
||||
public override string GetString(ReadOnlySpan<byte> data)
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ public override void CopyChangesFrom(SaveFile sav)
|
|||
0 => "-Base", // Vanilla
|
||||
1 => "-IoA", // DLC 1: Isle of Armor
|
||||
2 => "-CT", // DLC 2: Crown Tundra
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(SaveRevision)),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(SaveRevision), SaveRevision, null),
|
||||
};
|
||||
|
||||
public override bool ChecksumsValid => true;
|
||||
|
|
@ -111,7 +111,7 @@ private void Initialize()
|
|||
0 => (Legal.MaxMoveID_8_O0, Legal.MaxSpeciesID_8_O0, Legal.MaxItemID_8_O0, Legal.MaxAbilityID_8_O0),
|
||||
1 => (Legal.MaxMoveID_8_R1, Legal.MaxSpeciesID_8_R1, Legal.MaxItemID_8_R1, Legal.MaxAbilityID_8_R1),
|
||||
2 => (Legal.MaxMoveID_8_R2, Legal.MaxSpeciesID_8_R2, Legal.MaxItemID_8_R2, Legal.MaxAbilityID_8_R2),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(SaveRevision)),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(SaveRevision), SaveRevision, null),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ public override void CopyChangesFrom(SaveFile sav)
|
|||
0 => "-Base", // Vanilla
|
||||
1 => "-TM", // Teal Mask
|
||||
2 => "-ID", // Indigo Disk
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(SaveRevision)),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(SaveRevision), SaveRevision, null),
|
||||
};
|
||||
|
||||
public override bool ChecksumsValid => true;
|
||||
|
|
@ -118,7 +118,7 @@ private void Initialize()
|
|||
0 => (Legal.MaxMoveID_9_T0, Legal.MaxSpeciesID_9_T0, Legal.MaxItemID_9_T0, Legal.MaxAbilityID_9_T0),
|
||||
1 => (Legal.MaxMoveID_9_T1, Legal.MaxSpeciesID_9_T1, Legal.MaxItemID_9_T1, Legal.MaxAbilityID_9_T1),
|
||||
2 => (Legal.MaxMoveID_9_T2, Legal.MaxSpeciesID_9_T2, Legal.MaxItemID_9_T2, Legal.MaxAbilityID_9_T2),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(SaveRevision)),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(SaveRevision), SaveRevision, null),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,8 @@ public override void CopyChangesFrom(SaveFile sav)
|
|||
{
|
||||
0 => "-Base", // Vanilla
|
||||
1 => "-MD", // Mega Dimension
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(SaveRevision)),
|
||||
2 => "-EOL", // End of Life
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(SaveRevision), SaveRevision, null),
|
||||
};
|
||||
|
||||
public override bool ChecksumsValid => true;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
|
@ -33,6 +34,7 @@ protected SAV_BEEF([ConstantExpected] int size, [ConstantExpected] int biOffset)
|
|||
/// <summary>
|
||||
/// Timestamp that the save file was last saved at (Secure Value)
|
||||
/// </summary>
|
||||
[TypeConverter(typeof(TypeConverterU64))]
|
||||
public ulong TimeStampCurrent
|
||||
{
|
||||
get => ReadUInt64LittleEndian(Data[BlockInfoOffset..]);
|
||||
|
|
@ -42,6 +44,7 @@ public ulong TimeStampCurrent
|
|||
/// <summary>
|
||||
/// Timestamp that the save file was saved at prior to the <see cref="TimeStampCurrent"/> (Secure Value)
|
||||
/// </summary>
|
||||
[TypeConverter(typeof(TypeConverterU64))]
|
||||
public ulong TimeStampPrevious
|
||||
{
|
||||
get => ReadUInt64LittleEndian(Data[(BlockInfoOffset + 8)..]);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
|
|
@ -19,13 +20,14 @@ private static int GetMemberOffset(int index)
|
|||
|
||||
private Memory<byte> GetMemberSlice(int index)
|
||||
=> Raw.Slice(GetMemberOffset(index), HallFame3PKM.SIZE);
|
||||
private HallFame3PKM GetMember(int index) => new(GetMemberSlice(index), Japanese);
|
||||
|
||||
public HallFame3PKM GetMember(int index) => new(GetMemberSlice(index), Japanese);
|
||||
|
||||
public HallFame3PKM[] Team
|
||||
{
|
||||
get
|
||||
{
|
||||
var team = new HallFame3PKM[6];
|
||||
var team = new HallFame3PKM[Count];
|
||||
for (int i = 0; i < Count; i++)
|
||||
team[i] = GetMember(i);
|
||||
return team;
|
||||
|
|
@ -49,13 +51,26 @@ public static HallFame3Entry[] GetEntries(SAV3 sav)
|
|||
|
||||
public static void SetEntries(SAV3 sav, HallFame3Entry[] entries)
|
||||
{
|
||||
byte[] data = sav.GetHallOfFameData();
|
||||
Span<byte> data = sav.GetHallOfFameData();
|
||||
Debug.Assert(data.Length >= MaxLength);
|
||||
|
||||
for (int i = 0; i < entries.Length; i++)
|
||||
entries[i].Data.CopyTo(data.AsSpan(i * SIZE));
|
||||
entries[i].Data.CopyTo(data[(i * SIZE)..]);
|
||||
sav.SetHallOfFameData(data);
|
||||
}
|
||||
|
||||
public void CopyFrom(IReadOnlyList<PKM> party)
|
||||
{
|
||||
var length = Math.Min(Count, party.Count);
|
||||
for (int i = 0; i < length; i++)
|
||||
GetMember(i).CopyFrom(party[i]);
|
||||
}
|
||||
|
||||
public void CopyFrom(HallFame3Entry entry)
|
||||
{
|
||||
for (int i = 0; i < Count; i++)
|
||||
GetMember(i).CopyFrom(entry.GetMember(i));
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class HallFame3PKM(Memory<byte> Raw, bool Japanese) : ISpeciesForm
|
||||
|
|
@ -104,4 +119,22 @@ public ushort Species
|
|||
};
|
||||
|
||||
public bool IsShiny => ShinyUtil.GetIsShiny3(ID32, PID);
|
||||
|
||||
public void CopyFrom(PKM pk)
|
||||
{
|
||||
Species = pk.Species;
|
||||
Level = pk.CurrentLevel;
|
||||
PID = pk.EncryptionConstant;
|
||||
ID32 = pk.ID32;
|
||||
pk.NicknameTrash.CopyTo(NicknameTrash);
|
||||
}
|
||||
|
||||
public void CopyFrom(HallFame3PKM pk)
|
||||
{
|
||||
Species = pk.Species;
|
||||
Level = pk.Level;
|
||||
PID = pk.PID;
|
||||
ID32 = pk.ID32;
|
||||
pk.NicknameTrash.CopyTo(NicknameTrash);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
public interface IGen3Wonder
|
||||
{
|
||||
int WonderOffset { get; }
|
||||
WonderNews3 WonderNews { get; set; }
|
||||
WonderCard3 WonderCard { get; set; }
|
||||
WonderCard3Extra WonderCardExtra { get; set; }
|
||||
}
|
||||
|
|
@ -1,29 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed class Record3(SAV3 SAV)
|
||||
public static class Record3
|
||||
{
|
||||
public uint GetRecord(int record) => ReadUInt32LittleEndian(SAV.Large[GetRecordOffset(record)..]) ^ SAV.SecurityKey;
|
||||
public void SetRecord(int record, uint value) => WriteUInt32LittleEndian(SAV.Large[GetRecordOffset(record)..], value ^ SAV.SecurityKey);
|
||||
|
||||
private int GetRecordOffset(int record)
|
||||
{
|
||||
var baseOffset = GetOffset(SAV.Version);
|
||||
var offset = baseOffset + (4 * record);
|
||||
return offset;
|
||||
}
|
||||
|
||||
public static int GetOffset(GameVersion version) => version switch
|
||||
{
|
||||
GameVersion.RS or GameVersion.R or GameVersion.S => 0x1540,
|
||||
GameVersion.E => 0x159C,
|
||||
GameVersion.FRLG or GameVersion.FR or GameVersion.LG => 0x1200,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(version), version, null),
|
||||
};
|
||||
|
||||
private static Type GetEnumType(GameVersion version) => version switch
|
||||
{
|
||||
GameVersion.RS or GameVersion.R or GameVersion.S => typeof(RecID3RuSa),
|
||||
|
|
@ -41,7 +22,7 @@ public static IList<ComboItem> GetItems(SAV3 sav)
|
|||
var names = GetEnumNames(version);
|
||||
var values = GetEnumValues(version);
|
||||
|
||||
var result = new ComboItem[values.Length];
|
||||
var result = new ComboItem[values.Length - 1]; // exclude NUM_GAME_STATS
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
{
|
||||
var replaced = names[i].Replace('_', ' ');
|
||||
|
|
@ -110,7 +91,7 @@ public enum RecID3RuSa
|
|||
USED_DAYCARE = 47,
|
||||
RODE_CABLE_CAR = 48,
|
||||
ENTERED_HOT_SPRINGS = 49,
|
||||
// NUM_GAME_STATS = 50
|
||||
NUM_GAME_STATS = 50,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -175,7 +156,7 @@ public enum RecID3Emerald
|
|||
BERRY_CRUSH_WITH_FRIENDS = 51,
|
||||
|
||||
// NUM_USED_GAME_STATS = 52,
|
||||
// NUM_GAME_STATS = 64
|
||||
NUM_GAME_STATS = 64,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -239,5 +220,5 @@ public enum RecID3FRLG
|
|||
UNION_WITH_FRIENDS = 50,
|
||||
BERRY_CRUSH_WITH_FRIENDS = 51,
|
||||
|
||||
// NUM_GAME_STATS = 64,
|
||||
NUM_GAME_STATS = 64,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,24 +3,13 @@
|
|||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed class Roamer3 : IContestStats
|
||||
public sealed record Roamer3(Memory<byte> Raw, bool IsGlitched) : IContestStats
|
||||
{
|
||||
public const int SIZE = 0x14;
|
||||
public bool IsGlitched { get; }
|
||||
private readonly Memory<byte> Raw;
|
||||
public const int SIZE = 0x14; // +8 bytes of unused
|
||||
private Span<byte> Data => Raw.Span;
|
||||
|
||||
public Roamer3(SAV3 sav)
|
||||
public Roamer3(ISaveBlock3Large large) : this(large.RoamerData, large is not SaveBlock3LargeE)
|
||||
{
|
||||
var offset = sav switch
|
||||
{
|
||||
SAV3RS => 0x3144,
|
||||
SAV3E => 0x31DC,
|
||||
_ => 0x30D0, // FRLG
|
||||
};
|
||||
var buffer = sav.LargeBuffer;
|
||||
Raw = buffer.Slice(offset, SIZE);
|
||||
IsGlitched = sav is not SAV3E;
|
||||
}
|
||||
|
||||
public uint IV32
|
||||
|
|
@ -53,7 +42,7 @@ public byte CurrentLevel
|
|||
set => Data[12] = value;
|
||||
}
|
||||
|
||||
public int Status { get => Data[0x0D]; set => Data[0x0D] = (byte)value; }
|
||||
public byte Status { get => Data[0x0D]; set => Data[0x0D] = (byte)value; }
|
||||
|
||||
public byte ContestCool { get => Data[0x0E]; set => Data[0x0E] = value; }
|
||||
public byte ContestBeauty { get => Data[0x0F]; set => Data[0x0F] = value; }
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ public int RegistryStatus
|
|||
public string OriginalTrainerName
|
||||
{
|
||||
get => StringConverter3.GetString(OriginalTrainerTrash, Language);
|
||||
set => StringConverter3.SetString(OriginalTrainerTrash, value, 7, Language, StringConverterOption.ClearFF);
|
||||
set => StringConverter3.SetString(OriginalTrainerTrash, value, 7, Language, StringConverterOption.None);
|
||||
}
|
||||
|
||||
public int OriginalTrainerClass => Data[9] % 5;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ namespace PKHeX.Core;
|
|||
/// </summary>
|
||||
public abstract class PlayerData5(SAV5 sav, Memory<byte> raw) : SaveBlock<SAV5>(sav, raw)
|
||||
{
|
||||
private Span<byte> OriginalTrainerTrash => Data.Slice(4, 0x10);
|
||||
public Span<byte> OriginalTrainerTrash => Data.Slice(4, 0x10);
|
||||
|
||||
public string OT
|
||||
{
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ public int Language
|
|||
set => Data[0x2D] = (byte)value;
|
||||
}
|
||||
|
||||
private Span<byte> OriginalTrainerTrash => Data.Slice(0x48, 0x1A);
|
||||
public Span<byte> OriginalTrainerTrash => Data.Slice(0x48, 0x1A);
|
||||
|
||||
public string OT
|
||||
{
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user