Refactoring: Rework saveblock to be Memory<byte> based (#4200)

This commit is contained in:
Kurt 2024-03-03 23:13:16 -06:00 committed by GitHub
parent 2b63c4b013
commit fa80dac2ac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
311 changed files with 5669 additions and 4525 deletions

View file

@ -119,7 +119,7 @@ public static class BallApplicator
return items.Length;
}
private static readonly Ball[] BallList = (Ball[])Enum.GetValues(typeof(Ball));
private static readonly Ball[] BallList = Enum.GetValues<Ball>();
private static int MaxBallSpanAlloc => BallList.Length;
static BallApplicator()
@ -129,7 +129,7 @@ public static class BallApplicator
Span<Ball> all = stackalloc Ball[BallList.Length - exclude.Length];
all = all[..FillExcept(all, exclude, BallList)];
var colors = (PersonalColor[])Enum.GetValues(typeof(PersonalColor));
var colors = Enum.GetValues<PersonalColor>();
foreach (var color in colors)
{
int c = (int)color;

View file

@ -1,7 +1,7 @@
namespace PKHeX.Core;
namespace PKHeX.Core;
public abstract class EventUnlocker<T> where T : SaveFile
public abstract class EventUnlocker<T>(T sav)
where T : SaveFile
{
protected T SAV { get; }
protected EventUnlocker(T sav) => SAV = sav;
protected T SAV { get; } = sav;
}

View file

@ -28,14 +28,24 @@ public sealed class EventBlockDiff<T, T2> : IEventWorkDiff where T : class, IEve
return;
var s1 = SaveUtil.GetVariantSAV(f1);
var s2 = SaveUtil.GetVariantSAV(f2);
if (s1 == null || s2 == null || s1.GetType() != s2.GetType() || s1 is not T t1 || s2 is not T t2)
if (s1 == null || s2 == null || s1.GetType() != s2.GetType() || GetBlock(s1) is not { } t1 || GetBlock(s2) is not { } t2)
{
Message = DifferentGameGroup;
return;
}
Diff(t1, t2);
}
private static T? GetBlock(SaveFile s1)
{
if (s1 is T t1)
return t1;
if (s1 is IEventFlagProvider37 p1)
return p1.EventWork as T;
return null;
}
private static EventWorkDiffCompatibility SanityCheckSaveInfo(T s1, T s2)
{
if (s1.GetType() != s2.GetType())

View file

@ -17,8 +17,6 @@ public sealed class FakeSaveFile : SaveFile
public override int MaxEV => 0;
public override ReadOnlySpan<ushort> HeldItems => Legal.HeldItems_RS;
public override int GetBoxOffset(int box) => -1;
public override string GetBoxName(int box) => $"Box {box:00}";
public override void SetBoxName(int box, ReadOnlySpan<char> value) { }
public override int MaxStringLengthOT => 5;
public override int MaxStringLengthNickname => 5;
public override ushort MaxMoveID => 5;

View file

@ -30,8 +30,17 @@ public sealed class BoxEdit(SaveFile SAV)
}
public int CurrentBox { get; private set; }
public int BoxWallpaper { get => SAV.GetBoxWallpaper(CurrentBox); set => SAV.SetBoxWallpaper(CurrentBox, value); }
public string BoxName { get => SAV.GetBoxName(CurrentBox); set => SAV.SetBoxName(CurrentBox, value); }
public int BoxWallpaper
{
get => (SAV as IBoxDetailWallpaper)?.GetBoxWallpaper(CurrentBox) ?? 0;
set => (SAV as IBoxDetailWallpaper)?.SetBoxWallpaper(CurrentBox, value);
}
public string BoxName
{
get => (SAV as IBoxDetailNameRead)?.GetBoxName(CurrentBox) ?? BoxDetailNameExtensions.GetDefaultBoxName(CurrentBox);
set => (SAV as IBoxDetailName)?.SetBoxName(CurrentBox, value);
}
public int MoveLeft(bool max = false)
{

View file

@ -115,7 +115,8 @@ public static class BoxExport
private static string GetFolderName(SaveFile sav, int box, BoxExportFolderNaming mode)
{
var boxName = Util.CleanFileName(sav.GetBoxName(box));
var boxName = sav is IBoxDetailNameRead r ? r.GetBoxName(box) : BoxDetailNameExtensions.GetDefaultBoxName(box);
boxName = Util.CleanFileName(boxName);
return mode switch
{
BoxExportFolderNaming.BoxName => boxName,

View file

@ -52,7 +52,7 @@ public static partial class Extensions
{
return
[
new(sav.Data.AsMemory(sav.GetDaycareSlotOffset(0, 2)), 0) {Type = StorageSlotType.Daycare }, // egg
new(sav.GetDaycareEgg(), 0) {Type = StorageSlotType.Daycare }, // egg
];
}
@ -71,8 +71,8 @@ public static partial class Extensions
var list = new List<SlotInfoMisc>();
if (sav.GTS > 0)
list.Add(new SlotInfoMisc(sav.GeneralBuffer[sav.GTS..], 0) { Type = StorageSlotType.GTS });
if (sav is SAV4HGSS)
list.Add(new SlotInfoMisc(sav.GeneralBuffer[SAV4HGSS.WalkerPair..], 1) {Type = StorageSlotType.Misc});
if (sav is SAV4HGSS hgss)
list.Add(new SlotInfoMisc(hgss.GeneralBuffer[SAV4HGSS.WalkerPair..], 1) {Type = StorageSlotType.Misc});
return list;
}
@ -80,19 +80,19 @@ public static partial class Extensions
{
var list = new List<SlotInfoMisc>
{
new(sav.Data, 0, sav.GTS) {Type = StorageSlotType.GTS},
new(sav.Data, 0, sav.PGL) { Type = StorageSlotType.Misc },
new(sav.GTS.Upload, 0) {Type = StorageSlotType.GTS},
new(sav.GlobalLink.Upload, 0) { Type = StorageSlotType.Misc },
new(sav.Data, 0, sav.GetBattleBoxSlot(0)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 1, sav.GetBattleBoxSlot(1)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 2, sav.GetBattleBoxSlot(2)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 3, sav.GetBattleBoxSlot(3)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 4, sav.GetBattleBoxSlot(4)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 5, sav.GetBattleBoxSlot(5)) {Type = StorageSlotType.BattleBox},
new(sav.BattleBox[0], 0) {Type = StorageSlotType.BattleBox},
new(sav.BattleBox[1], 1) {Type = StorageSlotType.BattleBox},
new(sav.BattleBox[2], 2) {Type = StorageSlotType.BattleBox},
new(sav.BattleBox[3], 3) {Type = StorageSlotType.BattleBox},
new(sav.BattleBox[4], 4) {Type = StorageSlotType.BattleBox},
new(sav.BattleBox[5], 5) {Type = StorageSlotType.BattleBox},
};
if (sav is SAV5B2W2 b2w2)
list.Insert(1, new(b2w2.Data, 0, b2w2.Fused) { Type = StorageSlotType.Fused });
list.Insert(1, new(b2w2.Forest.Fused, 0) { Type = StorageSlotType.Fused });
return list;
}
@ -101,16 +101,16 @@ public static partial class Extensions
{
return
[
new(sav.Data, 0, sav.GTS) {Type = StorageSlotType.GTS},
new(sav.Data, 0, sav.Fused) {Type = StorageSlotType.Fused},
new(sav.Data, 0, sav.SUBE.Give) {Type = StorageSlotType.Misc}, // Old Man
new(sav.GTS.Upload, 0) {Type = StorageSlotType.GTS},
new(sav.Fused[0], 0) {Type = StorageSlotType.Fused},
new(sav.SUBE.GiveSlot, 0) {Type = StorageSlotType.Misc}, // Old Man
new(sav.Data, 0, sav.GetBattleBoxSlot(0)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 1, sav.GetBattleBoxSlot(1)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 2, sav.GetBattleBoxSlot(2)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 3, sav.GetBattleBoxSlot(3)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 4, sav.GetBattleBoxSlot(4)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 5, sav.GetBattleBoxSlot(5)) {Type = StorageSlotType.BattleBox},
new(sav.BattleBox[0], 0) {Type = StorageSlotType.BattleBox},
new(sav.BattleBox[1], 1) {Type = StorageSlotType.BattleBox},
new(sav.BattleBox[2], 2) {Type = StorageSlotType.BattleBox},
new(sav.BattleBox[3], 3) {Type = StorageSlotType.BattleBox},
new(sav.BattleBox[4], 4) {Type = StorageSlotType.BattleBox},
new(sav.BattleBox[5], 5) {Type = StorageSlotType.BattleBox},
];
}
@ -118,16 +118,16 @@ public static partial class Extensions
{
return
[
new(sav.Data, 0, SAV6AO.GTS) {Type = StorageSlotType.GTS},
new(sav.Data, 0, SAV6AO.Fused) {Type = StorageSlotType.Fused},
new(sav.Data, 0, sav.SUBE.Give) {Type = StorageSlotType.Misc},
new(sav.GTS.Upload, 0) { Type = StorageSlotType.GTS },
new(sav.Fused[0], 0) { Type = StorageSlotType.Fused },
new(sav.SUBE.GiveSlot, 0) {Type = StorageSlotType.Misc},
new(sav.Data, 0, sav.GetBattleBoxSlot(0)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 1, sav.GetBattleBoxSlot(1)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 2, sav.GetBattleBoxSlot(2)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 3, sav.GetBattleBoxSlot(3)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 4, sav.GetBattleBoxSlot(4)) {Type = StorageSlotType.BattleBox},
new(sav.Data, 5, sav.GetBattleBoxSlot(5)) {Type = StorageSlotType.BattleBox},
new(sav.BattleBox[0], 0) {Type = StorageSlotType.BattleBox},
new(sav.BattleBox[1], 1) {Type = StorageSlotType.BattleBox},
new(sav.BattleBox[2], 2) {Type = StorageSlotType.BattleBox},
new(sav.BattleBox[3], 3) {Type = StorageSlotType.BattleBox},
new(sav.BattleBox[4], 4) {Type = StorageSlotType.BattleBox},
new(sav.BattleBox[5], 5) {Type = StorageSlotType.BattleBox},
];
}
@ -135,22 +135,21 @@ public static partial class Extensions
{
var list = new List<SlotInfoMisc>
{
new(sav.Data, 0, sav.AllBlocks[07].Offset) {Type = StorageSlotType.GTS},
new(sav.Data, 0, sav.GetFusedSlotOffset(0)) {Type = StorageSlotType.Fused},
new(sav.GTS.Upload, 0) {Type = StorageSlotType.GTS},
new(sav.Fused[0], 0, PartyFormat: true) {Type = StorageSlotType.Fused},
};
if (sav is SAV7USUM uu)
{
list.AddRange(
[
new SlotInfoMisc(uu.Data, 1, uu.GetFusedSlotOffset(1)) {Type = StorageSlotType.Fused},
new SlotInfoMisc(uu.Data, 2, uu.GetFusedSlotOffset(2)) {Type = StorageSlotType.Fused},
new SlotInfoMisc(uu.Fused[1], 1, PartyFormat: true) {Type = StorageSlotType.Fused},
new SlotInfoMisc(uu.Fused[2], 2, PartyFormat: true) {Type = StorageSlotType.Fused},
]);
var ba = uu.BattleAgency;
list.AddRange(
[
new SlotInfoMisc(uu.Data, 0, ba.GetSlotOffset(0)) {Type = StorageSlotType.Misc},
new SlotInfoMisc(uu.Data, 1, ba.GetSlotOffset(1)) {Type = StorageSlotType.Misc},
new SlotInfoMisc(uu.Data, 2, ba.GetSlotOffset(2)) {Type = StorageSlotType.Misc},
new SlotInfoMisc(uu.BattleAgency[0], 0) {Type = StorageSlotType.Misc},
new SlotInfoMisc(uu.BattleAgency[1], 1) {Type = StorageSlotType.Misc},
new SlotInfoMisc(uu.BattleAgency[2], 2) {Type = StorageSlotType.Misc},
]);
}
@ -158,15 +157,15 @@ public static partial class Extensions
return list;
for (int i = 0; i < ResortSave7.ResortCount; i++)
list.Add(new SlotInfoMisc(sav.Data, i, sav.ResortSave.GetResortSlotOffset(i)) { Type = StorageSlotType.Resort });
list.Add(new SlotInfoMisc(sav.ResortSave[i], i) { Type = StorageSlotType.Resort });
return list;
}
private static List<SlotInfoMisc> GetExtraSlots7b(SAV7b sav)
private static List<SlotInfoMisc> GetExtraSlots7b(ISaveBlock7b sav)
{
return
[
new(sav.Data, 0, sav.Blocks.GetBlockOffset(BelugaBlockIndex.Daycare) + 8, true) {Type = StorageSlotType.Daycare},
new(sav.Daycare.Stored, 0) {Type = StorageSlotType.Daycare},
];
}
@ -176,20 +175,20 @@ public static partial class Extensions
var dc = sav.Daycare;
var list = new List<SlotInfoMisc>
{
new(fused.Data, 0, Fused8.GetFusedSlotOffset(0), true) {Type = StorageSlotType.Fused},
new(fused.Data, 1, Fused8.GetFusedSlotOffset(1), true) {Type = StorageSlotType.Fused},
new(fused.Data, 2, Fused8.GetFusedSlotOffset(2), true) {Type = StorageSlotType.Fused},
new(fused[0], 0, true) {Type = StorageSlotType.Fused},
new(fused[1], 1, true) {Type = StorageSlotType.Fused},
new(fused[2], 2, true) {Type = StorageSlotType.Fused},
new(dc.Data, 0, Daycare8.GetDaycareSlotOffset(0, 0)) {Type = StorageSlotType.Daycare},
new(dc.Data, 0, Daycare8.GetDaycareSlotOffset(0, 1)) {Type = StorageSlotType.Daycare},
new(dc.Data, 0, Daycare8.GetDaycareSlotOffset(1, 0)) {Type = StorageSlotType.Daycare},
new(dc.Data, 0, Daycare8.GetDaycareSlotOffset(1, 1)) {Type = StorageSlotType.Daycare},
new(dc[0], 0) {Type = StorageSlotType.Daycare},
new(dc[1], 1) {Type = StorageSlotType.Daycare},
new(dc[2], 2) {Type = StorageSlotType.Daycare},
new(dc[3], 3) {Type = StorageSlotType.Daycare},
};
if (sav is SAV8SWSH {SaveRevision: >= 2} s8)
{
var block = s8.Blocks.GetBlockSafe(SaveBlockAccessor8SWSH.KFusedCalyrex);
var c = new SlotInfoMisc(block.Data, 3, 0, true) {Type = StorageSlotType.Fused};
var c = new SlotInfoMisc(block.Data, 3, true) {Type = StorageSlotType.Fused};
list.Insert(3, c);
}
@ -200,15 +199,15 @@ public static partial class Extensions
{
return
[
new(sav.Data, 0, sav.UgSaveData.GetSlotOffset(0), true) { Type = StorageSlotType.Misc },
new(sav.Data, 1, sav.UgSaveData.GetSlotOffset(1), true) { Type = StorageSlotType.Misc },
new(sav.Data, 2, sav.UgSaveData.GetSlotOffset(2), true) { Type = StorageSlotType.Misc },
new(sav.Data, 3, sav.UgSaveData.GetSlotOffset(3), true) { Type = StorageSlotType.Misc },
new(sav.Data, 4, sav.UgSaveData.GetSlotOffset(4), true) { Type = StorageSlotType.Misc },
new(sav.Data, 5, sav.UgSaveData.GetSlotOffset(5), true) { Type = StorageSlotType.Misc },
new(sav.Data, 6, sav.UgSaveData.GetSlotOffset(6), true) { Type = StorageSlotType.Misc },
new(sav.Data, 7, sav.UgSaveData.GetSlotOffset(7), true) { Type = StorageSlotType.Misc },
new(sav.Data, 8, sav.UgSaveData.GetSlotOffset(8), true) { Type = StorageSlotType.Misc },
new(sav.UgSaveData[0], 0, true) { Type = StorageSlotType.Misc },
new(sav.UgSaveData[1], 1, true) { Type = StorageSlotType.Misc },
new(sav.UgSaveData[2], 2, true) { Type = StorageSlotType.Misc },
new(sav.UgSaveData[3], 3, true) { Type = StorageSlotType.Misc },
new(sav.UgSaveData[4], 4, true) { Type = StorageSlotType.Misc },
new(sav.UgSaveData[5], 5, true) { Type = StorageSlotType.Misc },
new(sav.UgSaveData[6], 6, true) { Type = StorageSlotType.Misc },
new(sav.UgSaveData[7], 7, true) { Type = StorageSlotType.Misc },
new(sav.UgSaveData[8], 8, true) { Type = StorageSlotType.Misc },
];
}
@ -223,18 +222,18 @@ public static partial class Extensions
var list = new List<SlotInfoMisc>
{
// Ride Legend
new(sav.BoxInfo.Data.AsMemory(afterBox), 0, true, Mutable: true) { Type = StorageSlotType.Party },
new(sav.BoxInfo.Raw.Slice(afterBox, PokeCrypto.SIZE_9PARTY), 0, true, Mutable: true) { Type = StorageSlotType.Party },
};
var block = sav.Blocks.GetBlock(SaveBlockAccessor9SV.KFusedCalyrex);
list.Add(new(block.Data, 0, 0, true) { Type = StorageSlotType.Fused });
list.Add(new(block.Data, 0, true) { Type = StorageSlotType.Fused });
if (sav.Blocks.TryGetBlock(SaveBlockAccessor9SV.KFusedKyurem, out var kyurem))
list.Add(new(kyurem.Data, 1, 0, true) { Type = StorageSlotType.Fused });
list.Add(new(kyurem.Data, 1, true) { Type = StorageSlotType.Fused });
if (sav.Blocks.TryGetBlock(SaveBlockAccessor9SV.KFusedNecrozmaS, out var solgaleo))
list.Add(new(solgaleo.Data, 2, 0, true) { Type = StorageSlotType.Fused });
list.Add(new(solgaleo.Data, 2, true) { Type = StorageSlotType.Fused });
if (sav.Blocks.TryGetBlock(SaveBlockAccessor9SV.KFusedNecrozmaM, out var lunala))
list.Add(new(lunala.Data, 3, 0, true) { Type = StorageSlotType.Fused });
list.Add(new(lunala.Data, 3, true) { Type = StorageSlotType.Fused });
return list;
}
}

View file

@ -38,9 +38,11 @@ public sealed class SlotCache : IComparable<SlotCache>
SAV = sav;
}
private string GetBoxName(int box) => SAV is IBoxDetailNameRead r ? r.GetBoxName(box) : BoxDetailNameExtensions.GetDefaultBoxName(box);
public string Identify() => GetFileName() + Source switch
{
SlotInfoBox box => $"[{box.Box + 1:00}] ({SAV.GetBoxName(box.Box)})-{box.Slot + 1:00}: {Entity.FileName}",
SlotInfoBox box => $"[{box.Box + 1:00}] ({GetBoxName(box.Box)})-{box.Slot + 1:00}: {Entity.FileName}",
SlotInfoFile file => $"File: {file.Path}",
SlotInfoMisc misc => $"{misc.Type}-{misc.Slot}: {Entity.FileName}",
SlotInfoParty party => $"Party: {party.Slot}: {Entity.FileName}",

View file

@ -7,21 +7,11 @@ namespace PKHeX.Core;
/// </summary>
public sealed record SlotInfoMisc(Memory<byte> Data, int Slot, bool PartyFormat = false, bool Mutable = false) : ISlotInfo
{
public SlotInfoMisc(SaveFile sav, int slot, int offset, bool party = false) : this(GetBuffer(sav)[offset..], slot, party) { }
public SlotInfoMisc(byte[] data, int slot, int offset, bool party = false) : this(data.AsMemory(offset), slot, party) { }
public SlotOrigin Origin => PartyFormat ? SlotOrigin.Party : SlotOrigin.Box;
public bool CanWriteTo(SaveFile sav) => Mutable;
public WriteBlockedMessage CanWriteTo(SaveFile sav, PKM pk) => Mutable ? WriteBlockedMessage.None : WriteBlockedMessage.InvalidDestination;
public StorageSlotType Type { get; init; }
private static Memory<byte> GetBuffer(SaveFile sav) => sav switch
{
SAV4 s => s.GeneralBuffer,
SAV3 s3 => s3.Large,
_ => sav.Data,
};
public bool WriteTo(SaveFile sav, PKM pk, PKMImportSetting setting = PKMImportSetting.UseDefault)
{
var span = Data.Span;

View file

@ -18,7 +18,7 @@ public static class GameUtil
private static GameVersion[] GetValidGameVersions()
{
var all = (GameVersion[])Enum.GetValues(typeof(GameVersion));
var all = Enum.GetValues<GameVersion>();
var valid = Array.FindAll(all, IsValidSavedVersion);
Array.Reverse(valid);
return valid;

View file

@ -21,7 +21,7 @@ public static class EncounterMovesetGenerator
/// </summary>
public static void ResetFilters() => PriorityList = GetAllGroups();
private static EncounterTypeGroup[] GetAllGroups() => (EncounterTypeGroup[])Enum.GetValues(typeof(EncounterTypeGroup));
private static EncounterTypeGroup[] GetAllGroups() => Enum.GetValues<EncounterTypeGroup>();
/// <summary>
/// Gets possible <see cref="IEncounterable"/> objects that allow all moves requested to be learned.

View file

@ -966,7 +966,7 @@ public static class FormConverter
public static string[] GetFormArgumentStrings(ushort species) => species switch
{
(int)Alcremie => Enum.GetNames(typeof(AlcremieDecoration)),
(int)Alcremie => Enum.GetNames<AlcremieDecoration>(),
_ => EMPTY,
};
}

View file

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
namespace PKHeX.Core;

View file

@ -17,4 +17,9 @@ public interface ISaveBlock5BW
Entralink5 Entralink { get; }
Musical5 Musical { get; }
Encount5 Encount { get; }
EventWork5 EventWork { get; }
BattleBox5 BattleBox { get; }
GlobalLink5 GlobalLink { get; }
GTS5 GTS { get; }
AdventureInfo5 AdventureInfo { get; }
}

View file

@ -1,4 +1,4 @@
namespace PKHeX.Core;
namespace PKHeX.Core;
/// <summary>
/// Interface for Accessing named blocks within a Generation 6 save file.
@ -8,4 +8,5 @@ public interface ISaveBlock6AO : ISaveBlock6Main
Misc6AO Misc { get; }
Zukan6AO Zukan { get; }
SecretBase6Block SecretBase { get; }
BerryField6AO BerryField { get; }
}

View file

@ -1,4 +1,4 @@
namespace PKHeX.Core;
namespace PKHeX.Core;
/// <summary>
/// Interface for Accessing named blocks within a Generation 6 save file.
@ -8,6 +8,8 @@ public interface ISaveBlock6Main : ISaveBlock6Core
{
Puff6 Puff { get; }
OPower6 OPower { get; }
GTS6 GTS { get; }
UnionPokemon6 Fused { get; }
LinkBlock6 Link { get; }
BoxLayout6 BoxLayout { get; }
BattleBox6 BattleBox { get; }

View file

@ -1,4 +1,4 @@
namespace PKHeX.Core;
namespace PKHeX.Core;
/// <summary>
/// Interface for Accessing named blocks within a Generation 7 save file.
@ -25,5 +25,7 @@ public interface ISaveBlock7Main
ResortSave7 ResortSave { get; }
FieldMenu7 FieldMenu { get; }
FashionBlock7 Fashion { get; }
HallOfFame7 Fame { get; }
EventWork7 EventWork { get; }
UnionPokemon7 Fused { get; }
GTS7 GTS { get; }
}

View file

@ -6,7 +6,7 @@ namespace PKHeX.Core;
/// <remarks>Blocks common for <see cref="SAV7b"/></remarks>
public interface ISaveBlock7b
{
MyItem Items { get; }
MyItem7b Items { get; }
Misc7b Misc { get; }
Zukan7b Zukan { get; }
MyStatus7b Status { get; }
@ -15,6 +15,7 @@ public interface ISaveBlock7b
EventWork7b EventWork { get; }
PokeListHeader Storage { get; }
WB7Records GiftRecords { get; }
Daycare7b Daycare { get; }
CaptureRecords Captured { get; }
GoParkStorage Park { get; }
PlayerGeoLocation7b PlayerGeoLocation { get; }

View file

@ -9,6 +9,14 @@ namespace PKHeX.Core;
/// </summary>
public abstract class SCBlockAccessor : ISaveBlockAccessor<SCBlock>
{
public static SCBlock GetBlock(IReadOnlyList<SCBlock> blocks, uint key) => Find(blocks, key);
public static SCBlock GetBlockSafe(IReadOnlyList<SCBlock> blocks, uint key) => FindOrDefault(blocks, key);
public static bool TryGetBlock(IReadOnlyList<SCBlock> blocks, uint key, [NotNullWhen(true)] out SCBlock? block) => TryFind(blocks, key, out block);
protected static SCBlock Block<T>(T sav, uint key) where T : ISCBlockArray => GetBlock(sav.AllBlocks, key);
protected static SCBlock BlockSafe<T>(T sav, uint key) where T : ISCBlockArray
=> GetBlockSafe(sav.AllBlocks, key);
public abstract IReadOnlyList<SCBlock> BlockInfo { get; }
/// <summary> Checks if there is any <see cref="SCBlock"/> with the requested <see cref="key"/>. </summary>

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
namespace PKHeX.Core;
@ -87,20 +88,38 @@ public sealed class SaveBlockAccessor5B2W2(SAV5B2W2 sav)
];
public IReadOnlyList<BlockInfoNDS> BlockInfo => BlocksB2W2;
public BoxLayout5 BoxLayout { get; } = new(sav, 0x00000);
public PlayerData5 PlayerData { get; } = new(sav, 0x19400);
public MyItem Items { get; } = new MyItem5B2W2(sav, 0x18400);
public UnityTower5 UnityTower { get; } = new(sav, 0x19600);
public MysteryBlock5 Mystery { get; } = new(sav, 0x1C800);
public Chatter5 Chatter { get; } = new(sav, 0x1D500);
public Musical5 Musical { get; } = new(sav, 0x1F700);
public Daycare5 Daycare { get; } = new(sav, 0x20D00);
public Misc5 Misc { get; } = new Misc5B2W2(sav, 0x21100);
public Zukan5 Zukan { get; } = new(sav, 0x21400, 0x328); // form flags size is + 8 from B/W with new forms (Therians)
public Entralink5 Entralink { get; } = new Entralink5B2W2(sav, 0x21200);
public Encount5 Encount { get; } = new Encount5B2W2(sav, 0x21900);
public BattleSubway5 BattleSubway { get; } = new(sav, 0x21B00);
public PWTBlock5 PWT { get; } = new(sav, 0x23700);
public MedalList5 Medals { get; } = new(sav, 0x25300);
public FestaBlock5 Festa { get; } = new(sav, 0x25900);
public BoxLayout5 BoxLayout { get; } = new(sav, Block(sav, 0));
public MyItem5B2W2 Items { get; } = new(sav, Block(sav, 25));
public PlayerData5 PlayerData { get; } = new(sav, Block(sav, 27));
public UnityTower5 UnityTower { get; } = new(sav, Block(sav, 29));
public MysteryBlock5 Mystery { get; } = new(sav, Block(sav, 34));
public GlobalLink5 GlobalLink { get; } = new(sav, Block(sav, 35));
public Chatter5 Chatter { get; } = new(sav, Block(sav, 36));
public AdventureInfo5 AdventureInfo { get; } = new(sav, Block(sav, 37));
public Musical5 Musical { get; } = new(sav, Block(sav, 42));
public WhiteBlack5B2W2 Forest { get; } = new(sav, Block(sav, 43));
public EventWork5B2W2 EventWork { get; } = new(sav, Block(sav, 45));
public GTS5 GTS { get; } = new(sav, Block(sav, 46));
public BattleBox5 BattleBox { get; } = new(sav, Block(sav, 49));
public Daycare5 Daycare { get; } = new(sav, Block(sav, 50));
public Misc5B2W2 Misc { get; } = new(sav, Block(sav, 52));
public Entralink5B2W2 Entralink { get; } = new(sav, Block(sav, 53));
public Zukan5 Zukan { get; } = new(sav, Block(sav, 54), 0x328); // form flags size is + 8 from B/W with new forms (Therians)
public Encount5B2W2 Encount { get; } = new(sav, Block(sav, 55));
public BattleSubway5 BattleSubway { get; } = new(sav, Block(sav, 57));
public EntreeForest EntreeForest { get; } = new(sav, Block(sav, 60));
public PWTBlock5 PWT { get; } = new(sav, Block(sav, 63));
public MedalList5 Medals { get; } = new(sav, Block(sav, 68));
public FestaBlock5 Festa { get; } = new(sav, Block(sav, 70));
EventWork5 ISaveBlock5BW.EventWork => EventWork;
Encount5 ISaveBlock5BW.Encount => Encount;
MyItem ISaveBlock5BW.Items => Items;
Entralink5 ISaveBlock5BW.Entralink => Entralink;
Misc5 ISaveBlock5BW.Misc => Misc;
public static Memory<byte> Block(SAV5B2W2 sav, int index)
{
var block = BlocksB2W2[index];
return sav.Data.AsMemory(block.Offset, block.Length);
}
}

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
namespace PKHeX.Core;
@ -82,17 +83,35 @@ public sealed class SaveBlockAccessor5BW(SAV5BW sav) : ISaveBlockAccessor<BlockI
];
public IReadOnlyList<BlockInfoNDS> BlockInfo => BlocksBW;
public BoxLayout5 BoxLayout { get; } = new(sav, 0x00000);
public MyItem Items { get; } = new MyItem5BW(sav, 0x18400);
public PlayerData5 PlayerData { get; } = new(sav, 0x19400);
public UnityTower5 UnityTower { get; } = new(sav, 0x19600);
public MysteryBlock5 Mystery { get; } = new(sav, 0x1C800);
public Chatter5 Chatter { get; } = new(sav, 0x1D500);
public Musical5 Musical { get; } = new(sav, 0x1F700);
public Daycare5 Daycare { get; } = new(sav, 0x20E00);
public Misc5 Misc { get; } = new Misc5BW(sav, 0x21200);
public Entralink5 Entralink { get; } = new Entralink5BW(sav, 0x21300);
public Zukan5 Zukan { get; } = new(sav, 0x21600, 0x320);
public Encount5 Encount { get; } = new Encount5BW(sav, 0x21B00);
public BattleSubway5 BattleSubway { get; } = new(sav, 0x21D00);
public BoxLayout5 BoxLayout { get; } = new(sav, Block(sav, 0));
public MyItem5BW Items { get; } = new(sav, Block(sav, 25));
public PlayerData5 PlayerData { get; } = new(sav, Block(sav, 27));
public UnityTower5 UnityTower { get; } = new(sav, Block(sav, 29));
public MysteryBlock5 Mystery { get; } = new(sav, Block(sav, 34));
public GlobalLink5 GlobalLink { get; } = new(sav, Block(sav, 35));
public Chatter5 Chatter { get; } = new(sav, Block(sav, 36));
public AdventureInfo5 AdventureInfo { get; } = new(sav, Block(sav, 37));
public Musical5 Musical { get; } = new(sav, Block(sav, 42));
public WhiteBlack5BW Forest { get; } = new(sav, Block(sav, 43));
public EventWork5BW EventWork { get; } = new(sav, Block(sav, 45));
public GTS5 GTS { get; } = new(sav, Block(sav, 46));
public BattleBox5 BattleBox { get; } = new(sav, Block(sav, 49));
public Daycare5 Daycare { get; } = new(sav, Block(sav, 50));
public Misc5BW Misc { get; } = new(sav, Block(sav, 52));
public Entralink5BW Entralink { get; } = new(sav, Block(sav, 53));
public Zukan5 Zukan { get; } = new(sav, Block(sav, 55), 0x320);
public Encount5BW Encount { get; } = new(sav, Block(sav, 56));
public BattleSubway5 BattleSubway { get; } = new(sav, Block(sav, 58));
public EntreeForest EntreeForest { get; } = new(sav, Block(sav, 61));
EventWork5 ISaveBlock5BW.EventWork => EventWork;
Encount5 ISaveBlock5BW.Encount => Encount;
MyItem ISaveBlock5BW.Items => Items;
Entralink5 ISaveBlock5BW.Entralink => Entralink;
Misc5 ISaveBlock5BW.Misc => Misc;
public static Memory<byte> Block(SAV5BW sav, int index)
{
var block = BlocksBW[index];
return sav.Data.AsMemory(block.Offset, block.Length);
}
}

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
namespace PKHeX.Core;
@ -5,7 +6,7 @@ namespace PKHeX.Core;
/// <summary>
/// Information for Accessing individual blocks within a <see cref="SAV6AO"/>.
/// </summary>
public sealed class SaveBlockAccessor6AO : ISaveBlockAccessor<BlockInfo6>, ISaveBlock6Main
public sealed class SaveBlockAccessor6AO(SAV6AO sav) : ISaveBlockAccessor<BlockInfo6>, ISaveBlock6Main
{
public const int BlockMetadataOffset = SaveUtil.SIZE_G6ORAS - 0x200;
private const int boAO = BlockMetadataOffset;
@ -73,53 +74,42 @@ public sealed class SaveBlockAccessor6AO : ISaveBlockAccessor<BlockInfo6>, ISave
];
public IReadOnlyList<BlockInfo6> BlockInfo => BlocksAO;
public MyItem Items { get; }
public ItemInfo6 ItemInfo { get; }
public GameTime6 GameTime { get; }
public Situation6 Situation { get; }
public PlayTime6 Played { get; }
public MyStatus6 Status { get; }
public RecordBlock6 Records { get; }
public Puff6 Puff { get; } = new(sav, Block(sav, 0));
public MyItem6AO Items { get; } = new(sav, Block(sav, 1));
public ItemInfo6 ItemInfo { get; } = new(sav, Block(sav, 2));
public GameTime6 GameTime { get; } = new(sav, Block(sav, 3));
public Situation6 Situation { get; } = new(sav, Block(sav, 4));
public PlayTime6 Played { get; } = new(sav, Block(sav, 6));
public Misc6AO Misc { get; } = new(sav, Block(sav, 11));
public BoxLayout6 BoxLayout { get; } = new(sav, Block(sav, 12));
public BattleBox6 BattleBox { get; } = new(sav, Block(sav, 13));
public MyStatus6 Status { get; } = new(sav, Block(sav, 17));
public EventWork6 EventWork { get; } = new(sav, Block(sav, 19));
public Zukan6AO Zukan { get; } = new(sav, Block(sav, 20), 0x400);
public UnionPokemon6 Fused { get; } = new(sav, Block(sav, 22));
public ConfigSave6 Config { get; } = new(sav, Block(sav, 23));
public OPower6 OPower { get; } = new(sav, Block(sav, 25));
public GTS6 GTS { get; } = new(sav, Block(sav, 28));
public Encount6 Encount { get; } = new(sav, Block(sav, 31));
public MaisonBlock Maison { get; } = new(sav, Block(sav, 37));
public Daycare6AO Daycare { get; } = new(sav, Block(sav, 38));
public BerryField6AO BerryField { get; } = new(sav, Block(sav, 40));
public MysteryBlock6 MysteryGift { get; } = new(sav, Block(sav, 41));
public SubEventLog6AO SUBE { get; } = new(sav, Block(sav, 42));
public RecordBlock6AO Records { get; } = new(sav, Block(sav, 44));
public SuperTrainBlock SuperTrain { get; } = new(sav, Block(sav, 46));
public LinkBlock6 Link { get; } = new(sav, Block(sav, 48));
public SecretBase6Block SecretBase { get; } = new(sav, Block(sav, 54));
public SangoInfoBlock Sango { get; } = new(sav, Block(sav, 55));
public Zukan6AO Zukan { get; }
public Puff6 Puff { get; }
public BoxLayout6 BoxLayout { get; }
public BattleBox6 BattleBox { get; }
public OPower6 OPower { get; }
public MysteryBlock6 MysteryGift { get; }
public SangoInfoBlock Sango { get; }
public LinkBlock6 Link { get; }
public Misc6AO Misc { get; }
public SuperTrainBlock SuperTrain { get; }
public MaisonBlock Maison { get; }
public SubEventLog6 SUBE { get; }
public ConfigSave6 Config { get; }
public Encount6 Encount { get; }
public SecretBase6Block SecretBase { get; }
MyItem ISaveBlock6Core.Items => Items;
SubEventLog6 ISaveBlock6Main.SUBE => SUBE;
RecordBlock6 ISaveBlock6Core.Records=> Records;
public SaveBlockAccessor6AO(SAV6AO sav)
private static Memory<byte> Block(SAV6AO sav, int index)
{
Puff = new Puff6(sav, 0x0000);
Items = new MyItem6AO(sav, 0x00400);
ItemInfo = new ItemInfo6(sav, 0x1000);
GameTime = new GameTime6(sav, 0x01200);
Situation = new Situation6(sav, 0x01400);
Played = new PlayTime6(sav, 0x01800);
Misc = new Misc6AO(sav, 0x04200);
BoxLayout = new BoxLayout6(sav, 0x04400);
BattleBox = new BattleBox6(sav, 0x04A00);
Status = new MyStatus6(sav, 0x14000);
Zukan = new Zukan6AO(sav, 0x15000, 0x400);
Config = new ConfigSave6(sav, 0x16C00);
OPower = new OPower6(sav, 0x17400);
Encount = new Encount6(sav, 0x18800);
Maison = new MaisonBlock(sav, 0x1BA00);
MysteryGift = new MysteryBlock6(sav, 0x1CC00);
SUBE = new SubEventLog6AO(sav, 0x1E800);
Records = new RecordBlock6AO(sav, 0x1F400);
SuperTrain = new SuperTrainBlock(sav, 0x20200);
Link = new LinkBlock6(sav, 0x20E00);
SecretBase = new SecretBase6Block(sav, 0x23A00);
Sango = new SangoInfoBlock(sav, 0x2B600);
var data = sav.Data;
var block = BlocksAO[index];
return data.AsMemory(block.Offset, block.Length);
}
}

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
namespace PKHeX.Core;
@ -31,12 +32,23 @@ public sealed class SaveBlockAccessor6AODemo(SAV6AODemo sav) : ISaveBlockAccesso
];
public IReadOnlyList<BlockInfo6> BlockInfo => BlocksAODemo;
public MyItem Items { get; } = new MyItem6AO(sav, 0x00000);
public ItemInfo6 ItemInfo { get; } = new(sav, 0x00C00);
public GameTime6 GameTime { get; } = new(sav, 0x00E00);
public Situation6 Situation { get; } = new(sav, 0x01000);
public PlayTime6 Played { get; } = new(sav, 0x01400);
public MyStatus6 Status { get; } = new(sav, 0x03C00);
public RecordBlock6 Records { get; } = new RecordBlock6AO(sav, 0x05400);
public Misc6AO Misc { get; } = new(sav, 0x03A00);
public MyItem6AO Items { get; } = new(sav, Block(sav, 0));
public ItemInfo6 ItemInfo { get; } = new(sav, Block(sav, 1));
public GameTime6 GameTime { get; } = new(sav, Block(sav, 2));
public Situation6 Situation { get; } = new(sav, Block(sav, 3));
public PlayTime6 Played { get; } = new(sav, Block(sav, 5));
public Misc6AO Misc { get; } = new(sav, Block(sav, 8));
public MyStatus6 Status { get; } = new(sav, Block(sav, 9));
public EventWork6 EventWork { get; } = new(sav, Block(sav, 11));
public RecordBlock6AO Records { get; } = new(sav, Block(sav, 15));
MyItem ISaveBlock6Core.Items => Items;
RecordBlock6 ISaveBlock6Core.Records => Records;
private static Memory<byte> Block(SAV6AODemo sav, int index)
{
var data = sav.Data;
var block = BlocksAODemo[index];
return data.AsMemory(block.Offset, block.Length);
}
}

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
namespace PKHeX.Core;
@ -70,26 +71,41 @@ public sealed class SaveBlockAccessor6XY(SAV6XY sav) : ISaveBlockAccessor<BlockI
];
public IReadOnlyList<BlockInfo6> BlockInfo => BlocksXY;
public Puff6 Puff { get; } = new(sav, Block(sav, 0));
public MyItem6XY Items { get; } = new(sav, Block(sav, 1));
public ItemInfo6 ItemInfo { get; } = new(sav, Block(sav, 2));
public GameTime6 GameTime { get; } = new(sav, Block(sav, 3));
public Situation6 Situation { get; } = new(sav, Block(sav, 4));
public PlayTime6 Played { get; } = new(sav, Block(sav, 6));
public Fashion6XY Fashion { get; } = new(sav, Block(sav, 7));
public Misc6XY Misc { get; } = new(sav, Block(sav, 11));
public BoxLayout6 BoxLayout { get; } = new(sav, Block(sav, 12));
public BattleBox6 BattleBox { get; } = new(sav, Block(sav, 13));
public MyStatus6XY Status { get; } = new(sav, Block(sav, 17));
public EventWork6 EventWork { get; } = new(sav, Block(sav, 19));
public Zukan6XY Zukan { get; } = new(sav, Block(sav, 20), 0x3C8);
public UnionPokemon6 Fused { get; } = new(sav, Block(sav, 22));
public ConfigSave6 Config { get; } = new(sav, Block(sav, 23));
public OPower6 OPower { get; } = new(sav, Block(sav, 25));
public GTS6 GTS { get; } = new(sav, Block(sav, 28));
public Encount6 Encount { get; } = new(sav, Block(sav, 31));
public MaisonBlock Maison { get; } = new(sav, Block(sav, 37));
public Daycare6XY Daycare { get; } = new(sav, Block(sav, 38));
public MysteryBlock6 MysteryGift { get; } = new(sav, Block(sav, 41));
public SubEventLog6XY SUBE { get; } = new(sav, Block(sav, 42));
public RecordBlock6XY Records { get; } = new(sav, Block(sav, 44));
public SuperTrainBlock SuperTrain { get; } = new(sav, Block(sav, 46));
public LinkBlock6 Link { get; } = new(sav, Block(sav, 48));
public Puff6 Puff { get; } = new(sav, 0x00000);
public MyItem Items { get; } = new MyItem6XY(sav, 0x00400);
public ItemInfo6 ItemInfo { get; } = new(sav, 0x01000);
public GameTime6 GameTime { get; } = new(sav, 0x01200);
public Situation6 Situation { get; } = new(sav, 0x01400);
public PlayTime6 Played { get; } = new(sav, 0x01800);
public Fashion6XY Fashion { get; } = new(sav, 0x1A00);
public Misc6XY Misc { get; } = new(sav, 0x4200);
public BoxLayout6 BoxLayout { get; } = new(sav, 0x4400);
public BattleBox6 BattleBox { get; } = new(sav, 0x04A00);
public MyStatus6 Status { get; } = new MyStatus6XY(sav, 0x14000);
public Zukan6XY Zukan { get; } = new(sav, 0x15000, 0x3C8);
public ConfigSave6 Config { get; } = new(sav, 0x16200);
public OPower6 OPower { get; } = new(sav, 0x16A00);
public Encount6 Encount { get; } = new(sav, 0x17E00);
public MysteryBlock6 MysteryGift { get; } = new(sav, 0x1BC00);
public SubEventLog6 SUBE { get; } = new SubEventLog6XY(sav, 0x1D800);
public RecordBlock6 Records { get; } = new RecordBlock6XY(sav, 0x1E400);
public SuperTrainBlock SuperTrain { get; } = new(sav, 0x1F200);
public LinkBlock6 Link { get; } = new(sav, 0x1FE00);
public MaisonBlock Maison { get; } = new(sav, 0x1B000);
MyItem ISaveBlock6Core.Items => Items;
SubEventLog6 ISaveBlock6Main.SUBE => SUBE;
RecordBlock6 ISaveBlock6Core.Records => Records;
MyStatus6 ISaveBlock6Core.Status => Status;
private static Memory<byte> Block(SAV6XY sav, int i)
{
var data = sav.Data;
var block = BlocksXY[i];
return data.AsMemory(block.Offset, block.Length);
}
}

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
namespace PKHeX.Core;
@ -5,7 +6,7 @@ namespace PKHeX.Core;
/// <summary>
/// Information for Accessing individual blocks within a <see cref="SAV7SM"/>.
/// </summary>
public sealed class SaveBlockAccessor7SM : ISaveBlockAccessor<BlockInfo7>, ISaveBlock7SM
public sealed class SaveBlockAccessor7SM(SAV7SM sav) : ISaveBlockAccessor<BlockInfo7>, ISaveBlock7SM
{
public const int BlockMetadataOffset = SaveUtil.SIZE_G7SM - 0x200;
private const int boSM = BlockMetadataOffset;
@ -51,52 +52,39 @@ public sealed class SaveBlockAccessor7SM : ISaveBlockAccessor<BlockInfo7>, ISave
new(boSM, 36, 0x6BA00, 0x00200), // 36 TurtleSalmonSave
];
public SaveBlockAccessor7SM(SAV7SM sav)
{
var bi = BlockInfo;
Items = new MyItem7SM(sav, 0);
Situation = new Situation7(sav, bi[01].Offset);
MyStatus = new MyStatus7(sav, bi[03].Offset);
Fame = new HallOfFame7(sav, bi[05].Offset + 0x9C4);
Zukan = new Zukan7(sav, bi[06].Offset, 0x550);
Misc = new Misc7(sav, bi[09].Offset);
FieldMenu = new FieldMenu7(sav, bi[10].Offset);
Config = new ConfigSave7(sav, bi[11].Offset);
GameTime = new GameTime7(sav, bi[12].Offset);
BoxLayout = new BoxLayout7(sav, bi[13].Offset);
ResortSave = new ResortSave7(sav, bi[15].Offset);
Played = new PlayTime6(sav, bi[16].Offset);
Overworld = new FieldMoveModelSave7(sav, bi[17].Offset);
Fashion = new FashionBlock7(sav, bi[18].Offset);
Festa = new JoinFesta7(sav, bi[21].Offset);
PokeFinder = new PokeFinder7(sav, bi[26].Offset);
MysteryGift = new MysteryBlock7(sav, bi[27].Offset);
Records = new RecordBlock7SM(sav, bi[28].Offset);
BattleTree = new BattleTree7(sav, bi[32].Offset);
Daycare = new Daycare7(sav, bi[33].Offset);
}
public IReadOnlyList<BlockInfo7> BlockInfo => BlockInfoSM;
public MyItem Items { get; }
public MysteryBlock7 MysteryGift { get; }
public PokeFinder7 PokeFinder { get; }
public JoinFesta7 Festa { get; }
public Daycare7 Daycare { get; }
public RecordBlock6 Records { get; }
public PlayTime6 Played { get; }
public MyStatus7 MyStatus { get; }
public FieldMoveModelSave7 Overworld { get; }
public Situation7 Situation { get; }
public ConfigSave7 Config { get; }
public GameTime7 GameTime { get; }
public Misc7 Misc { get; }
public Zukan7 Zukan { get; }
public BoxLayout7 BoxLayout { get; }
public BattleTree7 BattleTree { get; }
public ResortSave7 ResortSave { get; }
public FieldMenu7 FieldMenu { get; }
public FashionBlock7 Fashion { get; }
public HallOfFame7 Fame { get; }
public MyItem7SM Items { get; } = new(sav, Block(sav, 0));
public Situation7 Situation { get; } = new(sav, Block(sav, 1));
public MyStatus7 MyStatus { get; } = new(sav, Block(sav, 3));
public EventWork7SM EventWork { get; } = new(sav, Block(sav, 5));
public Zukan7 Zukan { get; } = new(sav, Block(sav, 6), 0x550);
public GTS7 GTS { get; } = new(sav, Block(sav, 07));
public UnionPokemon7 Fused { get; } = new(sav, Block(sav, 8));
public Misc7 Misc { get; } = new(sav, Block(sav, 9));
public FieldMenu7 FieldMenu { get; } = new(sav, Block(sav, 10));
public ConfigSave7 Config { get; } = new(sav, Block(sav, 11));
public GameTime7 GameTime { get; } = new(sav, Block(sav, 12));
public BoxLayout7 BoxLayout { get; } = new(sav, Block(sav, 13));
public ResortSave7 ResortSave { get; } = new(sav, Block(sav, 15));
public PlayTime6 Played { get; } = new(sav, Block(sav, 16));
public FieldMoveModelSave7 Overworld { get; } = new(sav, Block(sav, 17));
public FashionBlock7 Fashion { get; } = new(sav, Block(sav, 18));
public JoinFesta7 Festa { get; } = new(sav, Block(sav, 21));
public PokeFinder7 PokeFinder { get; } = new(sav, Block(sav, 26));
public MysteryBlock7 MysteryGift { get; } = new(sav, Block(sav, 27));
public RecordBlock7SM Records { get; } = new(sav, Block(sav, 28));
public BattleTree7 BattleTree { get; } = new(sav, Block(sav, 32));
public Daycare7 Daycare { get; } = new(sav, Block(sav, 33));
MyItem ISaveBlock7Main.Items => Items;
EventWork7 ISaveBlock7Main.EventWork => EventWork;
RecordBlock6 ISaveBlock7Main.Records => Records;
private static Memory<byte> Block(SAV7SM sav, int index)
{
var data = sav.Data;
var block = BlockInfoSM[index];
return data.AsMemory(block.Offset, block.Length);
}
}

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
namespace PKHeX.Core;
@ -5,7 +6,7 @@ namespace PKHeX.Core;
/// <summary>
/// Information for Accessing individual blocks within a <see cref="SAV7USUM"/>.
/// </summary>
public sealed class SaveBlockAccessor7USUM : ISaveBlockAccessor<BlockInfo7>, ISaveBlock7USUM
public sealed class SaveBlockAccessor7USUM(SAV7USUM sav) : ISaveBlockAccessor<BlockInfo7>, ISaveBlock7USUM
{
public const int BlockMetadataOffset = SaveUtil.SIZE_G7USUM - 0x200;
private const int boUU = BlockMetadataOffset;
@ -53,54 +54,39 @@ public sealed class SaveBlockAccessor7USUM : ISaveBlockAccessor<BlockInfo7>, ISa
new(boUU, 38, 0x6C600, 0x00400), // 38 FinderStudioSave
];
public SaveBlockAccessor7USUM(SAV7USUM sav)
{
var bi = BlockInfo;
Items = new MyItem7USUM(sav, 0);
Situation = new Situation7(sav, bi[01].Offset);
MyStatus = new MyStatus7(sav, bi[03].Offset);
Fame = new HallOfFame7(sav, bi[05].Offset + 0xA3C);
Zukan = new Zukan7(sav, bi[06].Offset, 0x550);
Misc = new Misc7(sav, bi[09].Offset);
FieldMenu = new FieldMenu7(sav, bi[10].Offset);
Config = new ConfigSave7(sav, bi[11].Offset);
GameTime = new GameTime7(sav, bi[12].Offset);
BoxLayout = new BoxLayout7(sav, bi[13].Offset);
ResortSave = new ResortSave7(sav, bi[15].Offset);
Played = new PlayTime6(sav, bi[16].Offset);
Overworld = new FieldMoveModelSave7(sav, bi[17].Offset);
Fashion = new FashionBlock7(sav, bi[18].Offset);
Festa = new JoinFesta7(sav, bi[21].Offset);
PokeFinder = new PokeFinder7(sav, bi[26].Offset);
MysteryGift = new MysteryBlock7(sav, bi[27].Offset);
Records = new RecordBlock7USUM(sav, bi[28].Offset);
BattleTree = new BattleTree7(sav, bi[32].Offset);
Daycare = new Daycare7(sav, bi[33].Offset);
BattleAgency = new BattleAgency7(sav, bi[37].Offset);
}
public IReadOnlyList<BlockInfo7> BlockInfo => BlockInfoUSUM;
public MyItem7USUM Items { get; } = new(sav, Block(sav, 00));
public Situation7 Situation { get; } = new(sav, Block(sav, 01));
public MyStatus7 MyStatus { get; } = new(sav, Block(sav, 03));
public EventWork7USUM EventWork { get; } = new(sav, Block(sav, 5));
public Zukan7 Zukan { get; } = new(sav, Block(sav, 06), 0x550);
public GTS7 GTS { get; } = new(sav, Block(sav, 07));
public UnionPokemon7 Fused { get; } = new(sav, Block(sav, 08));
public Misc7 Misc { get; } = new(sav, Block(sav, 09));
public FieldMenu7 FieldMenu { get; } = new(sav, Block(sav, 10));
public ConfigSave7 Config { get; } = new(sav, Block(sav, 11));
public GameTime7 GameTime { get; } = new(sav, Block(sav, 12));
public BoxLayout7 BoxLayout { get; } = new(sav, Block(sav, 13));
public ResortSave7 ResortSave { get; } = new(sav, Block(sav, 15));
public PlayTime6 Played { get; } = new(sav, Block(sav, 16));
public FieldMoveModelSave7 Overworld { get; } = new(sav, Block(sav, 17));
public FashionBlock7 Fashion { get; } = new(sav, Block(sav, 18));
public JoinFesta7 Festa { get; } = new(sav, Block(sav, 21));
public PokeFinder7 PokeFinder { get; } = new(sav, Block(sav, 26));
public MysteryBlock7 MysteryGift { get; } = new(sav, Block(sav, 27));
public RecordBlock7USUM Records { get; } = new(sav, Block(sav, 28));
public BattleTree7 BattleTree { get; } = new(sav, Block(sav, 32));
public Daycare7 Daycare { get; } = new(sav, Block(sav, 33));
public BattleAgency7 BattleAgency { get; } = new(sav, Block(sav, 37));
public MyItem Items { get; }
public MysteryBlock7 MysteryGift { get; }
public PokeFinder7 PokeFinder { get; }
public JoinFesta7 Festa { get; }
public Daycare7 Daycare { get; }
public RecordBlock6 Records { get; }
public PlayTime6 Played { get; }
public MyStatus7 MyStatus { get; }
public FieldMoveModelSave7 Overworld { get; }
public Situation7 Situation { get; }
public ConfigSave7 Config { get; }
public GameTime7 GameTime { get; }
public Misc7 Misc { get; }
public Zukan7 Zukan { get; }
public BoxLayout7 BoxLayout { get; }
public BattleTree7 BattleTree { get; }
public ResortSave7 ResortSave { get; }
public FieldMenu7 FieldMenu { get; }
public FashionBlock7 Fashion { get; }
public HallOfFame7 Fame { get; }
public BattleAgency7 BattleAgency { get; }
MyItem ISaveBlock7Main.Items => Items;
EventWork7 ISaveBlock7Main.EventWork => EventWork;
RecordBlock6 ISaveBlock7Main.Records => Records;
private static Memory<byte> Block(SAV7USUM sav, int index)
{
var data = sav.Data;
var block = BlockInfoUSUM[index];
return data.AsMemory(block.Offset, block.Length);
}
}

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
namespace PKHeX.Core;
@ -5,7 +6,7 @@ namespace PKHeX.Core;
/// <summary>
/// Information for Accessing individual blocks within a <see cref="SAV7b"/>.
/// </summary>
public sealed class SaveBlockAccessor7b : ISaveBlockAccessor<BlockInfo7b>, ISaveBlock7b
public sealed class SaveBlockAccessor7b(SAV7b sav) : ISaveBlockAccessor<BlockInfo7b>, ISaveBlock7b
{
private const int boGG = 0xB8800 - 0x200; // nowhere near 1MB (savedata.bin size)
@ -36,40 +37,26 @@ public sealed class SaveBlockAccessor7b : ISaveBlockAccessor<BlockInfo7b>, ISave
public IReadOnlyList<BlockInfo7b> BlockInfo => BlockInfoGG;
public SaveBlockAccessor7b(SAV7b sav)
{
Zukan = new Zukan7b(sav, GetBlockOffset(BelugaBlockIndex.Zukan), 0x550);
Config = new ConfigSave7b(sav, GetBlockOffset(BelugaBlockIndex.ConfigSave));
Items = new MyItem7b(sav, GetBlockOffset(BelugaBlockIndex.MyItem));
Coordinates = new Coordinates7b(sav, GetBlockOffset(BelugaBlockIndex.Coordinates));
Storage = new PokeListHeader(sav, GetBlockOffset(BelugaBlockIndex.PokeListHeader));
Status = new MyStatus7b(sav, GetBlockOffset(BelugaBlockIndex.MyStatus));
Played = new PlayTime7b(sav, GetBlockOffset(BelugaBlockIndex.PlayTime));
Misc = new Misc7b(sav, GetBlockOffset(BelugaBlockIndex.Misc));
EventWork = new EventWork7b(sav, GetBlockOffset(BelugaBlockIndex.EventWork));
GiftRecords = new WB7Records(sav, GetBlockOffset(BelugaBlockIndex.WB7Record));
Captured = new CaptureRecords(sav, GetBlockOffset(BelugaBlockIndex.CaptureRecord));
FashionPlayer = new Fashion7b(sav, GetBlockOffset(BelugaBlockIndex.FashionPlayer));
FashionStarter = new Fashion7b(sav, GetBlockOffset(BelugaBlockIndex.FashionStarter));
Park = new GoParkStorage(sav, GetBlockOffset(BelugaBlockIndex.GoParkEntities));
PlayerGeoLocation = new PlayerGeoLocation7b(sav, GetBlockOffset(BelugaBlockIndex.PlayerGeoLocation));
}
public MyItem7b Items { get; } = new(sav, Block(sav, BelugaBlockIndex.MyItem));
public Coordinates7b Coordinates { get; } = new(sav, Block(sav, BelugaBlockIndex.Coordinates));
public Misc7b Misc { get; } = new(sav, Block(sav, BelugaBlockIndex.Misc));
public Zukan7b Zukan { get; } = new(sav, Block(sav, BelugaBlockIndex.Zukan), 0x550);
public MyStatus7b Status { get; } = new(sav, Block(sav, BelugaBlockIndex.MyStatus));
public PlayTime7b Played { get; } = new(sav, Block(sav, BelugaBlockIndex.PlayTime));
public ConfigSave7b Config { get; } = new(sav, Block(sav, BelugaBlockIndex.ConfigSave));
public EventWork7b EventWork { get; } = new(sav, Block(sav, BelugaBlockIndex.EventWork));
public PokeListHeader Storage { get; } = new(sav, Block(sav, BelugaBlockIndex.PokeListHeader), sav.State.Exportable);
public WB7Records GiftRecords { get; } = new(sav, Block(sav, BelugaBlockIndex.WB7Record));
public CaptureRecords Captured { get; } = new(sav, Block(sav, BelugaBlockIndex.CaptureRecord));
public Daycare7b Daycare { get; } = new(sav, Block(sav, BelugaBlockIndex.Daycare));
public Fashion7b FashionPlayer { get; } = new(sav, Block(sav, BelugaBlockIndex.FashionPlayer));
public Fashion7b FashionStarter { get; } = new(sav, Block(sav, BelugaBlockIndex.FashionStarter));
public GoParkStorage Park { get; } = new(sav, Block(sav, BelugaBlockIndex.GoParkEntities));
public PlayerGeoLocation7b PlayerGeoLocation { get; } = new(sav, Block(sav, BelugaBlockIndex.PlayerGeoLocation));
public MyItem Items { get; }
public Coordinates7b Coordinates { get; }
public Misc7b Misc { get; }
public Zukan7b Zukan { get; }
public MyStatus7b Status { get; }
public PlayTime7b Played { get; }
public ConfigSave7b Config { get; }
public EventWork7b EventWork { get; }
public PokeListHeader Storage { get; }
public WB7Records GiftRecords { get; }
public CaptureRecords Captured { get; }
public Fashion7b FashionPlayer { get; }
public Fashion7b FashionStarter { get; }
public GoParkStorage Park { get; }
public PlayerGeoLocation7b PlayerGeoLocation { get; }
public BlockInfo GetBlock(BelugaBlockIndex index) => BlockInfo[(int)index];
public int GetBlockOffset(BelugaBlockIndex index) => GetBlock(index).Offset;
public static Memory<byte> Block(SAV7b sav, BelugaBlockIndex index)
{
var block = BlockInfoGG[(int)index];
return sav.Data.AsMemory(block.Offset, block.Length);
}
}

View file

@ -4,36 +4,20 @@ namespace PKHeX.Core;
// ReSharper disable UnusedMember.Local
#pragma warning disable IDE0051, RCS1213 // Remove unused private members
public sealed class SaveBlockAccessor8LA : SCBlockAccessor, ISaveBlock8LA
public sealed class SaveBlockAccessor8LA(SAV8LA sav) : SCBlockAccessor, ISaveBlock8LA
{
public override IReadOnlyList<SCBlock> BlockInfo { get; }
public Party8a PartyInfo { get; }
public Box8 BoxInfo { get; }
public MyStatus8a MyStatus { get; }
public PokedexSave8a PokedexSave { get; }
public BoxLayout8a BoxLayout { get; }
public MyItem8a Items { get; }
public Epoch1970Value AdventureStart { get; }
public Coordinates8a Coordinates { get; }
public LastSaved8a LastSaved { get; }
public PlayerFashion8a FashionPlayer { get; }
public PlayTime8a Played { get; }
public SaveBlockAccessor8LA(SAV8LA sav)
{
BlockInfo = sav.AllBlocks;
BoxInfo = new Box8(sav, GetBlock(KBox));
PokedexSave = new PokedexSave8a(sav, GetBlock(KZukan));
BoxLayout = new BoxLayout8a(sav, GetBlock(KBoxLayout));
PartyInfo = new Party8a(sav, GetBlock(KParty));
MyStatus = new MyStatus8a(sav, GetBlock(KMyStatus));
Items = new MyItem8a(sav, GetBlock(KItemRegular));
AdventureStart = new Epoch1970Value(GetBlock(KAdventureStart));
LastSaved = new LastSaved8a(sav, GetBlock(KLastSaved));
Played = new PlayTime8a(sav, GetBlock(KPlayTime));
Coordinates = new Coordinates8a(sav, GetBlock(KCoordinates));
FashionPlayer = new PlayerFashion8a(sav, GetBlock(KFashionPlayer));
}
public override IReadOnlyList<SCBlock> BlockInfo { get; } = sav.AllBlocks;
public Party8a PartyInfo { get; } = new(sav, Block(sav, KParty));
public Box8 BoxInfo { get; } = new(sav, Block(sav, KBox));
public MyStatus8a MyStatus { get; } = new(sav, Block(sav, KMyStatus));
public PokedexSave8a PokedexSave { get; } = new(sav, Block(sav, KZukan));
public BoxLayout8a BoxLayout { get; } = new(sav, Block(sav, KBoxLayout));
public MyItem8a Items { get; } = new(sav, Block(sav, KItemRegular));
public Epoch1970Value AdventureStart { get; } = new(Block(sav, KAdventureStart));
public Coordinates8a Coordinates { get; } = new(sav, Block(sav, KCoordinates));
public LastSaved8a LastSaved { get; } = new(sav, Block(sav, KLastSaved));
public PlayerFashion8a FashionPlayer { get; } = new(sav, Block(sav, KFashionPlayer));
public PlayTime8a Played { get; } = new(sav, Block(sav, KPlayTime));
public int DetectRevision() => HasBlock(0x8184EFB4) ? 1 : 0;

View file

@ -7,54 +7,29 @@ namespace PKHeX.Core;
/// <summary>
/// Information for Accessing individual blocks within a <see cref="SAV8SWSH"/>.
/// </summary>
public sealed class SaveBlockAccessor8SWSH : SCBlockAccessor, ISaveBlock8Main
public sealed class SaveBlockAccessor8SWSH(SAV8SWSH sav) : SCBlockAccessor, ISaveBlock8Main
{
public override IReadOnlyList<SCBlock> BlockInfo { get; }
public Box8 BoxInfo { get; }
public Party8 PartyInfo { get; }
public MyItem8 Items { get; }
public Coordinates8 Coordinates { get; }
public MyStatus8 MyStatus { get; }
public Misc8 Misc { get; }
public Zukan8 Zukan { get; }
public BoxLayout8 BoxLayout { get; }
public PlayTime8 Played { get; }
public Fused8 Fused { get; }
public Daycare8 Daycare { get; }
public Record8 Records { get; }
public TrainerCard8 TrainerCard{ get; }
public FashionUnlock8 Fashion { get; }
public RaidSpawnList8 RaidGalar { get; }
public RaidSpawnList8 RaidArmor { get; }
public RaidSpawnList8 RaidCrown { get; }
public TitleScreen8 TitleScreen { get; }
public TeamIndexes8 TeamIndexes { get; }
public HallOfFameTime8 FameTime { get; }
public SaveBlockAccessor8SWSH(SAV8SWSH sav)
{
BlockInfo = sav.AllBlocks;
BoxInfo = new Box8(sav, GetBlock(KBox));
PartyInfo = new Party8(sav, GetBlock(KParty));
Items = new MyItem8(sav, GetBlock(KItem));
Coordinates = new Coordinates8(sav, GetBlock(KCoordinates));
Zukan = new Zukan8(sav, GetBlock(KZukan), GetBlockSafe(KZukanR1), GetBlockSafe(KZukanR2));
MyStatus = new MyStatus8(sav, GetBlock(KMyStatus));
Misc = new Misc8(sav, GetBlock(KMisc));
BoxLayout = new BoxLayout8(sav, GetBlock(KBoxLayout));
TrainerCard = new TrainerCard8(sav, GetBlock(KTrainerCard));
Played = new PlayTime8(sav, GetBlock(KPlayTime));
Fused = new Fused8(sav, GetBlock(KFused));
Daycare = new Daycare8(sav, GetBlock(KDaycare));
Records = new Record8(sav, GetBlock(KRecord));
Fashion = new FashionUnlock8(sav, GetBlock(KFashionUnlock));
RaidGalar = new RaidSpawnList8(sav, GetBlock(KRaidSpawnList), RaidSpawnList8.RaidCountLegal_O0);
RaidArmor = new RaidSpawnList8(sav, GetBlockSafe(KRaidSpawnListR1), RaidSpawnList8.RaidCountLegal_R1);
RaidCrown = new RaidSpawnList8(sav, GetBlockSafe(KRaidSpawnListR2), RaidSpawnList8.RaidCountLegal_R2);
TitleScreen = new TitleScreen8(sav, GetBlock(KTitleScreenTeam));
TeamIndexes = new TeamIndexes8(sav, GetBlock(KTeamIndexes), GetBlock(KTeamLocks));
FameTime = new HallOfFameTime8(sav, GetBlock(KEnteredHallOfFame));
}
public override IReadOnlyList<SCBlock> BlockInfo { get; } = sav.AllBlocks;
public Box8 BoxInfo { get; } = new(sav, Block(sav, KBox));
public Party8 PartyInfo { get; } = new(sav, Block(sav, KParty));
public MyItem8 Items { get; } = new(sav, Block(sav, KItem));
public Coordinates8 Coordinates { get; } = new(sav, Block(sav, KCoordinates));
public MyStatus8 MyStatus { get; } = new(sav, Block(sav, KMyStatus));
public Misc8 Misc { get; } = new(sav, Block(sav, KMisc));
public Zukan8 Zukan { get; } = new(sav, Block(sav, KZukan), BlockSafe(sav, KZukanR1), BlockSafe(sav, KZukanR2));
public BoxLayout8 BoxLayout { get; } = new(sav, Block(sav, KBoxLayout));
public PlayTime8 Played { get; } = new(sav, Block(sav, KPlayTime));
public Fused8 Fused { get; } = new(sav, Block(sav, KFused));
public Daycare8 Daycare { get; } = new(sav, Block(sav, KDaycare));
public Record8 Records { get; } = new(sav, Block(sav, KRecord));
public TrainerCard8 TrainerCard{ get; } = new(sav, Block(sav, KTrainerCard));
public FashionUnlock8 Fashion { get; } = new(sav, Block(sav, KFashionUnlock));
public RaidSpawnList8 RaidGalar { get; } = new(sav, Block(sav, KRaidSpawnList), RaidSpawnList8.RaidCountLegal_O0);
public RaidSpawnList8 RaidArmor { get; } = new(sav, BlockSafe(sav, KRaidSpawnListR1), RaidSpawnList8.RaidCountLegal_R1);
public RaidSpawnList8 RaidCrown { get; } = new(sav, BlockSafe(sav, KRaidSpawnListR2), RaidSpawnList8.RaidCountLegal_R2);
public TitleScreen8 TitleScreen { get; } = new(sav, Block(sav, KTitleScreenTeam));
public TeamIndexes8 TeamIndexes { get; } = new(sav, Block(sav, KTeamIndexes), Block(sav, KTeamLocks));
public HallOfFameTime8 FameTime { get; } = new(sav, Block(sav, KEnteredHallOfFame));
// Arrays (Blocks)
private const uint KTeamNames = 0x1920C1E4; // Team 1, 2...6 ((10 + terminator)*6 char16 strings)

View file

@ -9,71 +9,60 @@ namespace PKHeX.Core;
/// <summary>
/// Information for Accessing individual blocks within a <see cref="SAV9SV"/>.
/// </summary>
public sealed class SaveBlockAccessor9SV : SCBlockAccessor, ISaveBlock9Main
public sealed class SaveBlockAccessor9SV(SAV9SV sav) : SCBlockAccessor, ISaveBlock9Main
{
public override IReadOnlyList<SCBlock> BlockInfo { get; }
public Box8 BoxInfo { get; }
public Party9 PartyInfo { get; }
public MyItem9 Items { get; }
public MyStatus9 MyStatus { get; }
public BoxLayout9 BoxLayout { get; }
public PlayTime9 Played { get; }
public Zukan9 Zukan { get; }
public ConfigSave9 Config { get; }
public ConfigCamera9 ConfigCamera { get; }
public TeamIndexes8 TeamIndexes { get; }
public Epoch1900DateTimeValue LastSaved { get; }
public Epoch1970Value LastDateCycle { get; }
public PlayerFashion9 PlayerFashion { get; }
public PlayerAppearance9 PlayerAppearance { get; }
public RaidSpawnList9 RaidPaldea { get; }
public RaidSpawnList9 RaidKitakami { get; }
public RaidSpawnList9 RaidBlueberry { get; }
public RaidSevenStar9 RaidSevenStar { get; }
public Epoch1900DateValue EnrollmentDate { get; }
public BlueberryQuestRecord9 BlueberryQuestRecord { get; }
public BlueberryClubRoom9 BlueberryClubRoom { get; }
public override IReadOnlyList<SCBlock> BlockInfo { get; } = sav.AllBlocks;
public Box8 BoxInfo { get; } = new(sav, Block(sav, KBox));
public Party9 PartyInfo { get; } = new(sav, Block(sav, KParty));
public MyItem9 Items { get; } = new(sav, Block(sav, KItem));
public MyStatus9 MyStatus { get; } = new(sav, Block(sav, KMyStatus));
public BoxLayout9 BoxLayout { get; } = new(sav, Block(sav, KBoxLayout));
public PlayTime9 Played { get; } = new(sav, Block(sav, KPlayTime));
public Zukan9 Zukan { get; } = new(sav, Block(sav, KZukan), BlockSafe(sav, KZukanT1));
public ConfigSave9 Config { get; } = new(sav, Block(sav, KConfig));
public ConfigCamera9 ConfigCamera { get; } = new(sav, BlockSafe(sav, KConfigCamera));
public TeamIndexes8 TeamIndexes { get; } = new(sav, Block(sav, KTeamIndexes), Block(sav, KTeamLocks));
public Epoch1900DateTimeValue LastSaved { get; } = new(Block(sav, KLastSaved));
public Epoch1970Value LastDateCycle { get; } = new(Block(sav, KLastDateCycle));
public PlayerFashion9 PlayerFashion { get; } = new(sav, Block(sav, KCurrentClothing));
public PlayerAppearance9 PlayerAppearance { get; } = new(sav, Block(sav, KCurrentAppearance));
public RaidSpawnList9 RaidPaldea => Raid.Paldea;
public RaidSpawnList9 RaidKitakami => Raid.Kitakami;
public RaidSpawnList9 RaidBlueberry => Raid.Blueberry;
public RaidSevenStar9 RaidSevenStar { get; } = new(sav, Block(sav, KSevenStarRaidsCapture), BlockSafe(sav, KSevenStarRaidsDefeat));
public Epoch1900DateValue EnrollmentDate { get; } = new(Block(sav, KEnrollmentDate));
public BlueberryQuestRecord9 BlueberryQuestRecord { get; } = new(sav, BlockSafe(sav, KBlueberryQuestRecords));
public BlueberryClubRoom9 BlueberryClubRoom { get; } = new(sav, BlockSafe(sav, KBlueberryClubRoom));
public SaveBlockAccessor9SV(SAV9SV sav)
private Raid9 Raid { get; } = new(sav);
private class Raid9
{
BlockInfo = sav.AllBlocks;
BoxInfo = new Box8(sav, GetBlock(KBox));
PartyInfo = new Party9(sav, GetBlock(KParty));
Items = new MyItem9(sav, GetBlock(KItem));
BoxLayout = new BoxLayout9(sav, GetBlock(KBoxLayout));
MyStatus = new MyStatus9(sav, GetBlock(KMyStatus));
Played = new PlayTime9(sav, GetBlock(KPlayTime));
Zukan = new Zukan9(sav, GetBlock(KZukan), GetBlockSafe(KZukanT1));
Config = new ConfigSave9(sav, GetBlock(KConfig));
ConfigCamera = new ConfigCamera9(sav, GetBlockSafe(KConfigCamera));
TeamIndexes = new TeamIndexes8(sav, GetBlock(KTeamIndexes), GetBlock(KTeamLocks));
LastSaved = new Epoch1900DateTimeValue(GetBlock(KLastSaved));
LastDateCycle = new Epoch1970Value(GetBlock(KLastDateCycle));
PlayerFashion = new PlayerFashion9(sav, GetBlock(KCurrentClothing));
PlayerAppearance = new PlayerAppearance9(sav, GetBlock(KCurrentAppearance));
public RaidSpawnList9 Paldea { get; }
public RaidSpawnList9 Kitakami { get; }
public RaidSpawnList9 Blueberry { get; }
var raidPaldea = GetBlock(KTeraRaidPaldea);
RaidPaldea = new RaidSpawnList9(sav, raidPaldea, raidPaldea.Data, RaidSpawnList9.RaidCountLegal_T0, true);
if (TryGetBlock(KTeraRaidDLC, out var raidDLC))
public Raid9(SAV9SV sav)
{
var buffer = raidDLC.Data;
const int size = 0xC80;
var memKita = buffer.AsMemory(0, size);
var memBlue = buffer.AsMemory(size, size);
RaidKitakami = new RaidSpawnList9(sav, raidDLC, memKita, RaidSpawnList9.RaidCountLegal_T1, false);
RaidBlueberry = new RaidSpawnList9(sav, raidDLC, memBlue, RaidSpawnList9.RaidCountLegal_T2, false);
}
else
{
var fake = GetFakeBlock();
RaidKitakami = new RaidSpawnList9(sav, fake, default, RaidSpawnList9.RaidCountLegal_T1, false);
RaidBlueberry = new RaidSpawnList9(sav, fake, default, RaidSpawnList9.RaidCountLegal_T2, false);
}
var paldea = GetBlock(sav.AllBlocks, KTeraRaidPaldea);
Paldea = new RaidSpawnList9(sav, paldea, paldea.Data, RaidSpawnList9.RaidCountLegal_T0, true);
RaidSevenStar = new RaidSevenStar9(sav, GetBlock(KSevenStarRaidsCapture), GetBlockSafe(KSevenStarRaidsDefeat));
EnrollmentDate = new Epoch1900DateValue(GetBlock(KEnrollmentDate));
BlueberryQuestRecord = new BlueberryQuestRecord9(sav, GetBlockSafe(KBlueberryQuestRecords));
BlueberryClubRoom = new BlueberryClubRoom9(sav, GetBlockSafe(KBlueberryClubRoom));
if (TryGetBlock(sav.AllBlocks, KTeraRaidDLC, out var raidDLC))
{
var buffer = raidDLC.Data;
const int size = 0xC80;
var memKita = buffer.AsMemory(0, size);
var memBlue = buffer.AsMemory(size, size);
Kitakami = new RaidSpawnList9(sav, raidDLC, memKita, RaidSpawnList9.RaidCountLegal_T1, false);
Blueberry = new RaidSpawnList9(sav, raidDLC, memBlue, RaidSpawnList9.RaidCountLegal_T2, false);
}
else
{
var fake = GetFakeBlock();
Kitakami = new RaidSpawnList9(sav, fake, default, RaidSpawnList9.RaidCountLegal_T1, false);
Blueberry = new RaidSpawnList9(sav, fake, default, RaidSpawnList9.RaidCountLegal_T2, false);
}
}
}
// Arrays (Blocks)

View file

@ -7,7 +7,7 @@ namespace PKHeX.Core;
/// <summary>
/// Gen4 Extra Block Info
/// </summary>
public class BlockInfo4 : BlockInfo
public sealed class BlockInfo4 : BlockInfo
{
private const int SIZE_FOOTER = 0x10;
private readonly int FooterOffset;
@ -61,7 +61,7 @@ public class BlockInfo4 : BlockInfo
WriteUInt16LittleEndian(data[(FooterOffset + 14)..], chk);
}
protected void SetMagic(Span<byte> data, uint magic)
private void SetMagic(Span<byte> data, uint magic)
{
if (!IsInitialized(data))
return;

View file

@ -2,21 +2,14 @@ using System;
namespace PKHeX.Core;
public abstract class RecordBlock<T> : SaveBlock<T>, IRecordStatStorage where T : SaveFile
public abstract class RecordBlock<T>(T sav, Memory<byte> raw) : SaveBlock<T>(sav, raw), IRecordStatStorage
where T : SaveFile
{
protected abstract ReadOnlySpan<byte> RecordMax { get; }
public abstract int GetRecord(int recordID);
public abstract void SetRecord(int recordID, int value);
public int GetRecordMax(int recordID) => Records.GetMax(recordID, RecordMax);
public int GetRecordOffset(int recordID) => Records.GetOffset(Offset, recordID);
public int GetRecordOffset(int recordID) => Records.GetOffset(recordID);
public void AddRecord(int recordID, int count = 1) => SetRecord(recordID, GetRecord(recordID) + count);
protected RecordBlock(T sav) : base(sav)
{
}
protected RecordBlock(T sav, byte[] data) : base(sav, data)
{
}
}

View file

@ -1,3 +1,4 @@
using System;
using System.ComponentModel;
namespace PKHeX.Core;
@ -5,20 +6,18 @@ namespace PKHeX.Core;
/// <summary>
/// Base class for a savegame data reader.
/// </summary>
public abstract class SaveBlock<T>(T sav, byte[] data, int offset = 0) : IDataIndirect where T : SaveFile
public abstract class SaveBlock<T>(T sav, Memory<byte> raw) : IDataIndirect where T : SaveFile
{
protected readonly T SAV = sav;
[Browsable(false)] public byte[] Data { get; } = data;
[Browsable(false)] public int Offset { get; protected init; } = offset;
[Browsable(false)] protected Memory<byte> Raw => raw;
[Browsable(false)] public Span<byte> Data => raw.Span;
protected SaveBlock(T sav) : this(sav, sav.Data) { }
protected SaveBlock(T sav, int offset) : this(sav, sav.Data, offset) { }
public bool Equals(ReadOnlyMemory<byte> other) => other.Equals(raw);
}
public interface IDataIndirect
{
int Offset { get; }
byte[] Data { get; }
Span<byte> Data { get; }
bool Equals(ReadOnlyMemory<byte> other);
}

View file

@ -0,0 +1,211 @@
using System;
using System.Security.Cryptography;
using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
/// <summary>
/// Encryption logic used by Pokémon Colosseum
/// </summary>
public static class ColoCrypto
{
private const int sha1HashSize = 20;
public const int SLOT_SIZE = 0x1E000;
public const int SLOT_START = 0x6000;
public const int SLOT_COUNT = 3;
public const int SAVE_SIZE = SLOT_START + (SLOT_SIZE * SLOT_COUNT);
public static byte[] GetSlot(ReadOnlySpan<byte> fullEncrypted, int slotIndex)
{
ArgumentOutOfRangeException.ThrowIfNotEqual(fullEncrypted.Length, SAVE_SIZE);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual<uint>((uint)slotIndex, SLOT_COUNT);
var result = fullEncrypted.Slice(SLOT_START + (slotIndex * SLOT_SIZE), SLOT_SIZE).ToArray();
DecryptInPlace(result);
return result;
}
public static Memory<byte> GetSlot(Memory<byte> fullEncrypted, int slotIndex)
{
ArgumentOutOfRangeException.ThrowIfNotEqual(fullEncrypted.Length, SAVE_SIZE);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual<uint>((uint)slotIndex, SLOT_COUNT);
var result = fullEncrypted.Slice(SLOT_START + (slotIndex * SLOT_SIZE), SLOT_SIZE);
DecryptInPlace(result.Span);
return result;
}
public static void GetSlot(ReadOnlySpan<byte> fullEncrypted, int slotIndex, Span<byte> slotData)
{
ArgumentOutOfRangeException.ThrowIfNotEqual(slotData.Length, SLOT_SIZE);
ArgumentOutOfRangeException.ThrowIfNotEqual(fullEncrypted.Length, SAVE_SIZE);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual<uint>((uint)slotIndex, SLOT_COUNT);
var data = fullEncrypted.Slice(SLOT_START + (slotIndex * SLOT_SIZE), SLOT_SIZE);
data.CopyTo(slotData);
DecryptInPlace(slotData);
}
public static void SetSlot(Span<byte> fullEncrypted, int slotIndex, ReadOnlySpan<byte> slotData)
{
ArgumentOutOfRangeException.ThrowIfNotEqual(slotData.Length, SLOT_SIZE);
ArgumentOutOfRangeException.ThrowIfNotEqual(fullEncrypted.Length, SAVE_SIZE);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual<uint>((uint)slotIndex, SLOT_COUNT);
var data = fullEncrypted.Slice(SLOT_START + (slotIndex * SLOT_SIZE), SLOT_SIZE);
slotData.CopyTo(data);
EncryptInPlace(data);
}
public static void EncryptInPlace(Span<byte> slot)
{
ArgumentOutOfRangeException.ThrowIfNotEqual(slot.Length, SLOT_SIZE);
// Get updated save slot data
Span<byte> digest = stackalloc byte[sha1HashSize];
slot[^sha1HashSize..].CopyTo(digest);
EncryptColosseum(slot, digest);
}
public static void DecryptInPlace(Span<byte> slot)
{
ArgumentOutOfRangeException.ThrowIfNotEqual(slot.Length, SLOT_SIZE);
// Get updated save slot data
Span<byte> digest = stackalloc byte[sha1HashSize];
slot[^sha1HashSize..].CopyTo(digest);
DecryptColosseum(slot, digest);
}
// Encryption Algorithm:
// Use a 20-byte hash as the seed for an iterative xor & hash-decrypted routine.
// Decrypt: Hash encrypted bytes for next loop, then un-xor to the decrypted state.
// Encrypt: xor to the encrypted state, then hash for the next loop.
private static void EncryptColosseum(Span<byte> data, Span<byte> digest)
{
// At-rest key is NOT the initial key :)
for (int i = 0; i < digest.Length; i++)
digest[i] = (byte)~digest[i];
var span = data[0x18..^sha1HashSize];
while (span.Length >= sha1HashSize)
{
// Could cast to u32 but juggling that is not worth it.
for (int j = 0; j < digest.Length; j++)
span[j] ^= digest[j];
// Hash the encrypted bytes for the next loop
SHA1.HashData(span[..sha1HashSize], digest);
span = span[sha1HashSize..];
}
}
// The second to last 20 bytes of the slot doesn't appear to be used for anything.
// When we do our hash over 0x18..0x1DFD7, we spill over 0x10 bytes due to (length % 20) not being 0.
// Wonder what the correct behavior is here? :(
private static void DecryptColosseum(Span<byte> data, Span<byte> digest)
{
// At-rest key is NOT the initial key :)
for (int i = 0; i < digest.Length; i++)
digest[i] = (byte)~digest[i];
Span<byte> hash = stackalloc byte[sha1HashSize];
var span = data[0x18..^sha1HashSize];
while (span.Length >= sha1HashSize)
{
// Hash the encrypted bytes for the next loop
SHA1.HashData(span[..sha1HashSize], hash);
// Could cast to u32 but juggling that is not worth it.
for (int j = 0; j < digest.Length; j++)
span[j] ^= digest[j];
// for use in next loop
hash.CopyTo(digest);
span = span[sha1HashSize..];
}
}
private static int ComputeHeaderChecksum(Span<byte> header, Span<byte> hash)
{
int result = 0;
for (int i = 0; i < 0x18; i += 4)
result -= ReadInt32BigEndian(header[i..]);
result -= ReadInt32BigEndian(header[0x18..]) ^ ~ReadInt32BigEndian(hash);
result -= ReadInt32BigEndian(header[0x1C..]) ^ ~ReadInt32BigEndian(hash[4..]);
return result;
}
public static void SetChecksums(Span<byte> data)
{
ArgumentOutOfRangeException.ThrowIfNotEqual(data.Length, SLOT_SIZE);
var header = data[..0x20];
var payload = data[..^(2 * sha1HashSize)];
var hash = data[^sha1HashSize..];
var headerCHK = data[0x0C..];
// Clear Header Checksum
WriteInt32BigEndian(headerCHK, 0);
// Compute checksum of data
SHA1.HashData(payload, hash);
// Compute new header checksum
int newHC = ComputeHeaderChecksum(header, hash);
// Set Header Checksum
WriteInt32BigEndian(headerCHK, newHC);
}
/// <summary>
/// Checks if the checksums are valid. Needs to mutate the header, only temporarily (no changes when returned).
/// </summary>
public static (bool IsHeaderValid, bool IsBodyValid) IsChecksumValid(Span<byte> data)
{
ArgumentOutOfRangeException.ThrowIfNotEqual(data.Length, SLOT_SIZE);
var header = data[..0x20];
var payload = data[..^(2 * sha1HashSize)];
var storedHash = data[^sha1HashSize..];
var hc = header[0x0C..];
int oldHC = ReadInt32BigEndian(hc);
// Clear Header Checksum
WriteInt32BigEndian(hc, 0);
Span<byte> currentHash = stackalloc byte[sha1HashSize];
SHA1.HashData(payload, currentHash);
// Compute new header checksum
int newHC = ComputeHeaderChecksum(header, currentHash);
// Restore old header checksum
WriteInt32BigEndian(hc, oldHC);
bool isHeaderValid = newHC == oldHC;
bool isBodyValid = storedHash.SequenceEqual(currentHash);
return (isHeaderValid, isBodyValid);
}
public static (int Index, int Count) DetectLatest(ReadOnlySpan<byte> data)
{
// Scan all 3 save slots for the highest counter
ArgumentOutOfRangeException.ThrowIfNotEqual(data.Length, SaveUtil.SIZE_G3COLO);
int counter = -1, index = -1;
for (int i = 0; i < SLOT_COUNT; i++)
{
int slotOffset = SLOT_START + (i * SLOT_SIZE);
int SaveCounter = ReadInt32BigEndian(data[(slotOffset + 4)..]);
if (SaveCounter <= counter)
continue;
counter = SaveCounter;
index = i;
}
return (index, counter);
}
}

View file

@ -52,7 +52,7 @@ public static class MemeCrypto
public static bool VerifyMemeData(ReadOnlySpan<byte> input, out byte[] output)
{
foreach (MemeKeyIndex keyIndex in Enum.GetValues(typeof(MemeKeyIndex)))
foreach (MemeKeyIndex keyIndex in Enum.GetValues<MemeKeyIndex>())
{
if (VerifyMemeData(input, out output, keyIndex))
return true;

View file

@ -105,7 +105,7 @@ public sealed class SCBlockCompare
{
foreach (var b in blocks)
{
var match = list.FirstOrDefault(z => ReferenceEquals(z.Key.Data, b.Data));
var match = list.FirstOrDefault(z => z.Key.Equals(b.Data));
if (match.Value is not { } x)
continue;
ref var exist = ref CollectionsMarshal.GetValueRefOrNullRef(names, b.Key);

View file

@ -95,7 +95,7 @@ public sealed class SCBlockMetadata
// See if we have a Block object for this block
if (block.Data.Length != 0)
{
var obj = BlockList.FirstOrDefault(z => ReferenceEquals(z.Key.Data, block.Data));
var obj = BlockList.FirstOrDefault(z => z.Key.Equals(block.Data));
if (obj is not (null, null))
{
saveBlock = obj.Key;

View file

@ -7,7 +7,7 @@ namespace PKHeX.Core;
/// <summary>
/// Generation 1 <see cref="SaveFile"/> object.
/// </summary>
public sealed class SAV1 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWorkArray<byte>
public sealed class SAV1 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWorkArray<byte>, IBoxDetailName, IDaycareStorage
{
protected internal override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}";
public override string Extension => ".sav";
@ -108,9 +108,11 @@ public sealed class SAV1 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWo
DaycareOffset = GetPartyOffset(7);
// Enable Pokedex editing
PokeDex = 0;
}
private int DaycareOffset = -1;
public override bool HasPokeDex => true;
private void UnpackBox(int srcOfs, int destOfs, int boxSize, int boxIndex, PokeListType boxCapacity)
{
var boxData = Data.AsSpan(srcOfs, boxSize).ToArray();
@ -196,9 +198,9 @@ public sealed class SAV1 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWo
partyPL.Write().CopyTo(Data, Offsets.Party);
// Daycare is read-only, but in case it ever becomes editable, copy it back in.
Span<byte> rawDC = Data.AsSpan(GetDaycareSlotOffset(loc: 0, slot: 0), SIZE_STORED);
Span<byte> rawDC = Data.AsSpan(GetDaycareSlotOffset(index: 0), SIZE_STORED);
Span<byte> dc = stackalloc byte[1 + (2 * StringLength) + PokeCrypto.SIZE_1STORED];
dc[0] = IsDaycareOccupied(0, 0) == true ? (byte)1 : (byte)0;
dc[0] = IsDaycareOccupied(0) ? (byte)1 : (byte)0;
rawDC.Slice(2 + 1 + PokeCrypto.SIZE_1PARTY + StringLength, StringLength).CopyTo(dc[1..]);
rawDC.Slice(2 + 1 + PokeCrypto.SIZE_1PARTY, StringLength).CopyTo(dc[(1 + StringLength)..]);
rawDC.Slice(2 + 1, PokeCrypto.SIZE_1STORED).CopyTo(dc[(1 + (2 * StringLength))..]);
@ -241,7 +243,6 @@ public sealed class SAV1 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWo
public override int MaxIV => 15;
public override byte Generation => 1;
public override EntityContext Context => EntityContext.Gen1;
protected override int GiftCountMax => 0;
public override int MaxStringLengthOT => Japanese ? 5 : 7;
public override int MaxStringLengthNickname => Japanese ? 5 : 10;
public override int BoxSlotCount => Japanese ? 30 : 20;
@ -308,7 +309,6 @@ public sealed class SAV1 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWo
public bool Yellow => Starter == 0x54; // Pikachu
public byte Starter { get => Data[Offsets.Starter]; set => Data[Offsets.Starter] = value; }
public ref byte WramD72E => ref Data[Offsets.Starter + 0x17]; // offset relative to player starter
// bit0 of d72e
@ -440,31 +440,30 @@ public sealed class SAV1 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWo
set => value.SaveAll(Data);
}
public override int GetDaycareSlotOffset(int loc, int slot)
public int DaycareSlotCount => 1;
public Memory<byte> GetDaycareSlot(int index)
{
ArgumentOutOfRangeException.ThrowIfNotEqual(index, 0, nameof(index));
return Data.AsMemory(DaycareOffset, SIZE_STORED);
}
private int GetDaycareSlotOffset(int index)
{
ArgumentOutOfRangeException.ThrowIfNotEqual(index, 0, nameof(index));
return DaycareOffset;
}
public override uint? GetDaycareEXP(int loc, int slot)
public bool IsDaycareOccupied(int index)
{
return null;
ArgumentOutOfRangeException.ThrowIfNotEqual(index, 0, nameof(index));
return Data[Offsets.Daycare] == 0x01;
}
public override bool? IsDaycareOccupied(int loc, int slot)
public void SetDaycareOccupied(int index, bool occupied)
{
if (loc == 0 && slot == 0)
return Data[Offsets.Daycare] == 0x01;
return null;
}
public override void SetDaycareEXP(int loc, int slot, uint EXP)
{
// todo
}
public override void SetDaycareOccupied(int loc, int slot, bool occupied)
{
// todo
ArgumentOutOfRangeException.ThrowIfNotEqual(index, 0, nameof(index));
Data[Offsets.Daycare] = (byte)(occupied ? 0x01 : 0x00);
}
// Storage
@ -474,15 +473,8 @@ public sealed class SAV1 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWo
protected set => Data[Offsets.Party] = (byte)value;
}
public override int GetBoxOffset(int box)
{
return Data.Length - SIZE_RESERVED + (box * SIZE_BOX);
}
public override int GetPartyOffset(int slot)
{
return Data.Length - SIZE_RESERVED + (BoxCount * SIZE_BOX) + (slot * SIZE_STORED);
}
public override int GetBoxOffset(int box) => Data.Length - SIZE_RESERVED + (box * SIZE_BOX);
public override int GetPartyOffset(int slot) => Data.Length - SIZE_RESERVED + (BoxCount * SIZE_BOX) + (slot * SIZE_STORED);
public override int CurrentBox
{
@ -496,14 +488,14 @@ public sealed class SAV1 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWo
set => Data[Offsets.CurrentBoxIndex] = (byte)((Data[Offsets.CurrentBoxIndex] & 0x7F) | (byte)(value ? 0x80 : 0));
}
public override string GetBoxName(int box)
public string GetBoxName(int box)
{
if (Japanese)
return $"ボックス{box + 1}";
return $"BOX {box + 1}";
return BoxDetailNameExtensions.GetDefaultBoxNameJapanese(box);
return BoxDetailNameExtensions.GetDefaultBoxName(box);
}
public override void SetBoxName(int box, ReadOnlySpan<char> value)
public void SetBoxName(int box, ReadOnlySpan<char> value)
{
// Don't allow for custom box names
}

View file

@ -7,7 +7,7 @@ namespace PKHeX.Core;
/// <summary>
/// Generation 2 <see cref="SaveFile"/> object.
/// </summary>
public sealed class SAV2 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWorkArray<byte>
public sealed class SAV2 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWorkArray<byte>, IBoxDetailName, IDaycareStorage, IDaycareEggState
{
protected internal override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}";
public override string Extension => ".sav";
@ -134,13 +134,11 @@ public sealed class SAV2 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWo
daycare1.Write().CopyTo(Data, GetPartyOffset(7 + (0 * 2)));
daycare2.Write().CopyTo(Data, GetPartyOffset(7 + (1 * 2)));
daycare3.Write().CopyTo(Data, GetPartyOffset(7 + (2 * 2)));
DaycareOffset = Offsets.Daycare;
}
// Enable Pokedex editing
PokeDex = 0;
}
public override bool HasPokeDex => true;
private int EventFlag => Offsets.EventFlag;
private int EventWork => Offsets.EventWork;
@ -282,13 +280,11 @@ public sealed class SAV2 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWo
public override int MaxIV => 15;
public override byte Generation => 2;
public override EntityContext Context => EntityContext.Gen2;
protected override int GiftCountMax => 0;
public override int MaxStringLengthOT => Japanese || Korean ? 5 : 7;
public override int MaxStringLengthNickname => Japanese || Korean ? 5 : 10;
public override int BoxSlotCount => Japanese ? 30 : 20;
public override bool HasParty => true;
public override bool HasNamableBoxes => true;
private int StringLength => Japanese ? GBPKML.StringLengthJapanese : GBPKML.StringLengthNotJapan;
// Checksums
@ -579,12 +575,13 @@ public sealed class SAV2 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWo
return offset;
}
public override int GetDaycareSlotOffset(int loc, int slot) => GetPartyOffset(7 + (slot * 2));
public override uint? GetDaycareEXP(int loc, int slot) => null;
public override bool? IsDaycareOccupied(int loc, int slot) => (DaycareFlagByte(slot) & 1) != 0;
public override void SetDaycareEXP(int loc, int slot, uint EXP) { }
public int DaycareSlotCount => 2;
private int GetDaycareSlotOffset(int slot) => GetPartyOffset(7 + (slot * 2));
public Memory<byte> GetDaycareSlot(int slot) => Data.AsMemory(GetDaycareSlotOffset(slot), SIZE_STORED);
public Memory<byte> GetDaycareEgg() => Data.AsMemory(GetDaycareSlotOffset(2), SIZE_STORED);
public bool IsDaycareOccupied(int slot) => (DaycareFlagByte(slot) & 1) != 0;
public override void SetDaycareOccupied(int loc, int slot, bool occupied)
public void SetDaycareOccupied(int slot, bool occupied)
{
if (occupied)
DaycareFlagByte(slot) |= 1;
@ -623,7 +620,7 @@ public sealed class SAV2 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWo
set => Data[Offsets.CurrentBoxIndex] = (byte)((Data[Offsets.CurrentBoxIndex] & 0x7F) | (byte)(value ? 0x80 : 0));
}
public override string GetBoxName(int box) => GetString(GetBoxNameSpan(box));
public string GetBoxName(int box) => GetString(GetBoxNameSpan(box));
private Span<byte> GetBoxNameSpan(int box)
{
@ -631,7 +628,7 @@ public sealed class SAV2 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWo
return Data.AsSpan(Offsets.BoxNames + (box * len), len);
}
public override void SetBoxName(int box, ReadOnlySpan<char> value)
public void SetBoxName(int box, ReadOnlySpan<char> value)
{
var span = GetBoxNameSpan(box);
SetString(span, value, 8, StringConverterOption.Clear50);

View file

@ -6,7 +6,7 @@ namespace PKHeX.Core;
/// <summary>
/// Pokémon Stadium 2 (Pokémon Stadium GS in Japan)
/// </summary>
public sealed class SAV2Stadium : SAV_STADIUM
public sealed class SAV2Stadium : SAV_STADIUM, IBoxDetailName
{
public override int SaveRevision => Japanese ? 0 : 1;
public override string SaveRevisionString => Japanese ? "J" : "U";
@ -143,16 +143,25 @@ public sealed class SAV2Stadium : SAV_STADIUM
return $"{name} [{id:D5}:{str}]";
}
public override string GetBoxName(int box)
public string GetBoxName(int box)
{
var ofs = GetBoxOffset(box) - 0x10;
var boxNameSpan = Data.AsSpan(ofs, 0x10);
var str = GetString(boxNameSpan);
if (string.IsNullOrWhiteSpace(str))
return $"Box {box + 1}";
return BoxDetailNameExtensions.GetDefaultBoxName(box);
return str;
}
public void SetBoxName(int box, ReadOnlySpan<char> name)
{
if (name.Length > StringLength)
throw new ArgumentOutOfRangeException(nameof(name), "Box name is too long.");
var ofs = GetBoxOffset(box) - 0x10;
var boxNameSpan = Data.AsSpan(ofs, 0x10);
SetString(boxNameSpan, name, StringLength, StringConverterOption.None);
}
public override SlotGroup GetTeam(int team)
{
if ((uint)team >= TeamCount)

View file

@ -8,7 +8,7 @@ namespace PKHeX.Core;
/// <summary>
/// Generation 3 <see cref="SaveFile"/> object.
/// </summary>
public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37
public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37, IBoxDetailName, IBoxDetailWallpaper, IDaycareStorage, IDaycareEggState, IDaycareExperience
{
protected internal sealed override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}";
public sealed override string Extension => ".sav";
@ -35,8 +35,8 @@ public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37
// There's no harm having buffers larger than their actual size (per format).
// A checksum consuming extra zeroes does not change the prior checksum result.
public readonly byte[] Small = new byte[1 * SIZE_SECTOR_USED]; // [0x890 RS, 0xf24 FR/LG, 0xf2c E]
public readonly byte[] Large = new byte[4 * SIZE_SECTOR_USED]; //3+[0xc40 RS, 0xee8 FR/LG, 0xf08 E]
public readonly byte[] Small = new byte[1 * SIZE_SECTOR_USED]; // [0x890 RS, 0xf24 FR/LG, 0xf2c E]
public readonly byte[] Large = new byte[4 * SIZE_SECTOR_USED]; //3+[0xc40 RS, 0xee8 FR/LG, 0xf08 E]
public readonly byte[] Storage = new byte[9 * SIZE_SECTOR_USED]; // [0x83D0]
private readonly int ActiveSlot;
@ -64,9 +64,9 @@ public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37
var id = ReadInt16LittleEndian(data[(ofs + 0xFF4)..]);
switch (id)
{
case >=5: data.Slice(ofs, SIZE_SECTOR_USED).CopyTo(Storage.AsSpan((id - 5) * SIZE_SECTOR_USED)); break;
case >=1: data.Slice(ofs, SIZE_SECTOR_USED).CopyTo(Large .AsSpan((id - 1) * SIZE_SECTOR_USED)); break;
default: data.Slice(ofs, SIZE_SECTOR_USED).CopyTo(Small .AsSpan(0 )); break;
case >= 5: data.Slice(ofs, SIZE_SECTOR_USED).CopyTo(Storage.AsSpan((id - 5) * SIZE_SECTOR_USED)); break;
case >= 1: data.Slice(ofs, SIZE_SECTOR_USED).CopyTo(Large.AsSpan((id - 1) * SIZE_SECTOR_USED)); break;
default: data.Slice(ofs, SIZE_SECTOR_USED).CopyTo(Small.AsSpan(0)); break;
}
}
}
@ -81,9 +81,9 @@ public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37
var id = ReadInt16LittleEndian(data[(ofs + 0xFF4)..]);
switch (id)
{
case >=5: Storage.AsSpan((id - 5) * SIZE_SECTOR_USED, SIZE_SECTOR_USED).CopyTo(data[ofs..]); break;
case >=1: Large .AsSpan((id - 1) * SIZE_SECTOR_USED, SIZE_SECTOR_USED).CopyTo(data[ofs..]); break;
default: Small .AsSpan(0 , SIZE_SECTOR_USED).CopyTo(data[ofs..]); break;
case >= 5: Storage.AsSpan((id - 5) * SIZE_SECTOR_USED, SIZE_SECTOR_USED).CopyTo(data[ofs..]); break;
case >= 1: Large.AsSpan((id - 1) * SIZE_SECTOR_USED, SIZE_SECTOR_USED).CopyTo(data[ofs..]); break;
default: Small.AsSpan(0, SIZE_SECTOR_USED).CopyTo(data[ofs..]); break;
}
}
}
@ -176,8 +176,8 @@ public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37
/// <returns>New <see cref="SaveFile"/> object.</returns>
public SAV3 ForceLoad(GameVersion version) => version switch
{
GameVersion.R or GameVersion.S or GameVersion.RS => new SAV3RS(Data),
GameVersion.E => new SAV3E(Data),
GameVersion.R or GameVersion.S or GameVersion.RS => new SAV3RS(Data),
GameVersion.E => new SAV3E(Data),
GameVersion.FR or GameVersion.LG or GameVersion.FRLG => new SAV3FRLG(Data),
_ => throw new ArgumentOutOfRangeException(nameof(version)),
};
@ -188,7 +188,6 @@ public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37
public sealed override int MaxEV => EffortValues.Max255;
public sealed override byte Generation => 3;
public sealed override EntityContext Context => EntityContext.Gen3;
protected sealed override int GiftCountMax => 1;
public sealed override int MaxStringLengthOT => 7;
public sealed override int MaxStringLengthNickname => 10;
public sealed override int MaxMoney => 999999;
@ -284,7 +283,7 @@ public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37
for (int i = 0; i < COUNT_MAIN; i++)
{
if (!IsSectorValid(i))
list.Add($"Sector {i} @ {i*SIZE_SECTOR:X5} invalid.");
list.Add($"Sector {i} @ {i * SIZE_SECTOR:X5} invalid.");
}
if (Data.Length > SaveUtil.SIZE_G3RAW) // don't check HoF for half-sizes
@ -392,8 +391,8 @@ public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37
public void SetWork(int index, ushort value) => WriteUInt16LittleEndian(Large.AsSpan(EventWork)[(index * 2)..], value);
#endregion
public sealed override bool GetFlag(int offset, int bitIndex) => FlagUtil.GetFlag(Large, offset, bitIndex);
public sealed override void SetFlag(int offset, int bitIndex, bool value) => FlagUtil.SetFlag(Large, offset, bitIndex, value);
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);
protected abstract int BadgeFlagStart { get; }
public abstract uint Coin { get; set; }
@ -436,20 +435,21 @@ public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37
}
protected abstract InventoryPouch3[] GetItems();
protected abstract int PokeDex { get; }
public override bool HasPokeDex => true;
public int DaycareSlotCount => 2;
protected abstract int DaycareSlotSize { get; }
public sealed override uint? GetDaycareEXP(int loc, int slot) => ReadUInt32LittleEndian(Large.AsSpan(GetDaycareEXPOffset(slot)));
public sealed override void SetDaycareEXP(int loc, int slot, uint EXP) => WriteUInt32LittleEndian(Large.AsSpan(GetDaycareEXPOffset(slot)), EXP);
public sealed override bool? IsDaycareOccupied(int loc, int slot) => IsPKMPresent(Large.AsSpan(GetDaycareSlotOffset(loc, slot)));
public sealed override void SetDaycareOccupied(int loc, int slot, bool occupied) { /* todo */ }
public sealed override int GetDaycareSlotOffset(int loc, int slot) => DaycareOffset + (slot * DaycareSlotSize);
protected abstract int EggEventFlag { get; }
public sealed override bool? IsDaycareHasEgg(int loc) => GetEventFlag(EggEventFlag);
public sealed override void SetDaycareHasEgg(int loc, bool hasEgg) => SetEventFlag(EggEventFlag, hasEgg);
protected abstract int DaycareOffset { get; }
protected abstract int GetDaycareEXPOffset(int slot);
public Memory<byte> GetDaycareSlot(int slot) => Large.AsMemory(GetDaycareSlotOffset(slot), DaycareSlotSize);
public uint GetDaycareEXP(int index) => ReadUInt32LittleEndian(Large.AsSpan(GetDaycareEXPOffset(index)));
public void SetDaycareEXP(int index, uint value) => WriteUInt32LittleEndian(Large.AsSpan(GetDaycareEXPOffset(index)), value);
public bool IsDaycareOccupied(int slot) => IsPKMPresent(Large.AsSpan(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); }
#region Storage
public sealed override int GetBoxOffset(int box) => Box + 4 + (SIZE_STORED * box * COUNT_SLOTSPERBOX);
@ -460,7 +460,7 @@ public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37
set => Storage[0] = (byte)value;
}
public sealed override int GetBoxWallpaper(int box)
public int GetBoxWallpaper(int box)
{
if (box > COUNT_BOX)
return box;
@ -470,7 +470,7 @@ public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37
private const int COUNT_BOXNAME = 8 + 1;
public sealed override void SetBoxWallpaper(int box, int value)
public void SetBoxWallpaper(int box, int value)
{
if (box > COUNT_BOX)
return;
@ -478,20 +478,20 @@ public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37
Storage[offset] = (byte)value;
}
protected sealed override int GetBoxWallpaperOffset(int box)
protected int GetBoxWallpaperOffset(int box)
{
int offset = GetBoxOffset(COUNT_BOX);
offset += (COUNT_BOX * COUNT_BOXNAME) + box;
return offset;
}
public sealed override string GetBoxName(int box)
public string GetBoxName(int box)
{
int offset = GetBoxOffset(COUNT_BOX);
return StringConverter3.GetString(Storage.AsSpan(offset + (box * COUNT_BOXNAME), COUNT_BOXNAME), Japanese);
}
public sealed override void SetBoxName(int box, ReadOnlySpan<char> value)
public void SetBoxName(int box, ReadOnlySpan<char> value)
{
int offset = GetBoxOffset(COUNT_BOX);
var dest = Storage.AsSpan(offset + (box * COUNT_BOXNAME), COUNT_BOXNAME);

View file

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
@ -8,7 +7,7 @@ namespace PKHeX.Core;
/// <summary>
/// Generation 3 <see cref="SaveFile"/> object for Pokémon Colosseum saves.
/// </summary>
public sealed class SAV3Colosseum : SaveFile, IGCSaveFile
public sealed class SAV3Colosseum : SaveFile, IGCSaveFile, IBoxDetailName, IDaycareStorage, IDaycareExperience
{
protected internal override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}";
public override string Extension => this.GCExtension();
@ -16,6 +15,11 @@ public sealed class SAV3Colosseum : SaveFile, IGCSaveFile
public override ReadOnlySpan<ushort> HeldItems => Legal.HeldItems_RS;
public SAV3GCMemoryCard? MemoryCard { get; init; }
private readonly Memory<byte> Raw;
private new Span<byte> Data => Raw.Span;
protected override Span<byte> BoxBuffer => Data;
protected override Span<byte> PartyBuffer => Data;
// 3 Save files are stored
// 0x0000-0x6000 contains memory card data
// 0x6000-0x60000 contains the 3 save slots
@ -24,25 +28,20 @@ public sealed class SAV3Colosseum : SaveFile, IGCSaveFile
// Another SHA1 hash is 0x1DFD8, for 20 bytes. Unknown purpose.
// Checksum is used as the crypto key.
private const int SLOT_SIZE = 0x1E000;
private const int SLOT_START = 0x6000;
private const int SLOT_COUNT = 3;
private const int sha1HashSize = 20;
private int SaveCount = -1;
private int SaveIndex = -1;
private readonly int SaveCount = -1;
private readonly int SaveIndex = -1;
private readonly StrategyMemo StrategyMemo;
public const int MaxShadowID = 0x80; // 128
private int Memo;
private int DaycareOffset;
private readonly byte[] BAK;
private readonly bool Japanese;
public SAV3Colosseum(bool japanese = false) : base(SaveUtil.SIZE_G3COLO)
public SAV3Colosseum(bool japanese = false)
{
Japanese = japanese;
BAK = [];
Raw = new byte[ColoCrypto.SLOT_SIZE];
StrategyMemo = Initialize();
ClearBoxes();
}
@ -50,8 +49,10 @@ public sealed class SAV3Colosseum : SaveFile, IGCSaveFile
public SAV3Colosseum(byte[] data) : base(data)
{
Japanese = data[0] == 0x83; // Japanese game name first character
BAK = data;
InitializeData();
// Decrypt most recent save slot
(SaveIndex, SaveCount) = ColoCrypto.DetectLatest(data);
Raw = ColoCrypto.GetSlot(data.AsMemory(), SaveIndex);
StrategyMemo = Initialize();
}
@ -69,41 +70,15 @@ public sealed class SAV3Colosseum : SaveFile, IGCSaveFile
for (int i = 0; i < 6; i++)
{
var ofs = GetPartyOffset(i);
var span = Data.AsSpan(ofs);
var span = Data[ofs..];
if (ReadUInt16BigEndian(span) != 0) // species is at offset 0x00
PartyCount++;
}
var memo = new StrategyMemo(Data.AsSpan(Memo), xd: false);
var memo = new StrategyMemo(Data[Memo..], xd: false);
return memo;
}
private void InitializeData()
{
// Scan all 3 save slots for the highest counter
for (int i = 0; i < SLOT_COUNT; i++)
{
int slotOffset = SLOT_START + (i * SLOT_SIZE);
int SaveCounter = ReadInt32BigEndian(Data.AsSpan(slotOffset + 4));
if (SaveCounter <= SaveCount)
continue;
SaveCount = SaveCounter;
SaveIndex = i;
}
// Decrypt most recent save slot
{
int slotOffset = SLOT_START + (SaveIndex * SLOT_SIZE);
ReadOnlySpan<byte> slot = Data.AsSpan(slotOffset, SLOT_SIZE);
Span<byte> digest = stackalloc byte[sha1HashSize];
slot[^sha1HashSize..].CopyTo(digest);
// Decrypt Slot
Data = DecryptColosseum(slot, digest);
}
}
protected override byte[] GetFinalData()
{
var newFile = GetInnerData();
@ -118,18 +93,13 @@ public sealed class SAV3Colosseum : SaveFile, IGCSaveFile
private byte[] GetInnerData()
{
StrategyMemo.Write().CopyTo(Data, Memo);
StrategyMemo.Write().CopyTo(Data[Memo..]);
SetChecksums();
// Get updated save slot data
ReadOnlySpan<byte> slot = Data;
Span<byte> digest = stackalloc byte[sha1HashSize];
slot[^sha1HashSize..].CopyTo(digest);
byte[] newSAV = EncryptColosseum(slot, digest);
// Put save slot back in original save data
byte[] newFile = MemoryCard != null ? MemoryCard.ReadSaveGameData().ToArray() : (byte[])BAK.Clone();
Array.Copy(newSAV, 0, newFile, SLOT_START + (SaveIndex * SLOT_SIZE), newSAV.Length);
byte[] newFile = (byte[])base.Data.Clone();
ColoCrypto.SetSlot(newFile, SaveIndex, Data);
return newFile;
}
@ -151,7 +121,6 @@ public sealed class SAV3Colosseum : SaveFile, IGCSaveFile
public override int MaxEV => EffortValues.Max255;
public override byte Generation => 3;
public override EntityContext Context => EntityContext.Gen3;
protected override int GiftCountMax => 1;
public override int MaxStringLengthOT => 10; // as evident by Mattle Ho-Oh
public override int MaxStringLengthNickname => 10;
public override int MaxMoney => 9999999;
@ -159,109 +128,14 @@ public sealed class SAV3Colosseum : SaveFile, IGCSaveFile
public override int BoxCount => 3;
public override bool IsPKMPresent(ReadOnlySpan<byte> data) => EntityDetection.IsPresentGC(data);
private static byte[] EncryptColosseum(ReadOnlySpan<byte> input, Span<byte> digest)
{
ArgumentOutOfRangeException.ThrowIfNotEqual(input.Length, SLOT_SIZE);
byte[] output = input.ToArray();
// NOT key
for (int i = 0; i < digest.Length; i++)
digest[i] = (byte)~digest[i];
const int start = 0x18;
const int end = (SLOT_SIZE - (2 * sha1HashSize));
var crypt = output.AsSpan();
for (int i = start; i < end; i += sha1HashSize)
{
var slice = crypt.Slice(i, digest.Length);
for (int j = 0; j < digest.Length; j++)
slice[j] ^= digest[j];
SHA1.HashData(slice, digest); // update digest
}
return output;
}
private static byte[] DecryptColosseum(ReadOnlySpan<byte> input, Span<byte> digest)
{
ArgumentOutOfRangeException.ThrowIfNotEqual(input.Length, SLOT_SIZE);
byte[] output = input.ToArray();
// NOT key
for (int i = 0; i < digest.Length; i++)
digest[i] = (byte)~digest[i];
Span<byte> hash = stackalloc byte[sha1HashSize];
const int start = 0x18;
const int end = (SLOT_SIZE - (2 * sha1HashSize));
var crypt = output.AsSpan();
for (int i = start; i < end; i += sha1HashSize)
{
var slice = crypt.Slice(i, Math.Min(crypt.Length - i, sha1HashSize));
SHA1.HashData(slice, hash); // update digest
for (int j = 0; j < slice.Length; j++)
slice[j] ^= digest[j];
hash.CopyTo(digest); // for use in next loop
}
return output;
}
protected override void SetChecksums()
{
var data = Data.AsSpan();
var header = data[..0x20];
var payload = data[..(SLOT_SIZE - (2 * sha1HashSize))];
var hash = data[^sha1HashSize..];
var headerCHK = data[0x0C..];
// Clear Header Checksum
WriteInt32BigEndian(headerCHK, 0);
// Compute checksum of data
SHA1.HashData(payload, hash);
// Compute new header checksum
int newHC = ComputeHeaderChecksum(header, hash);
// Set Header Checksum
WriteInt32BigEndian(headerCHK, newHC);
}
private static int ComputeHeaderChecksum(Span<byte> header, Span<byte> hash)
{
int result = 0;
for (int i = 0; i < 0x18; i += 4)
result -= ReadInt32BigEndian(header[i..]);
result -= ReadInt32BigEndian(header[0x18..]) ^ ~ReadInt32BigEndian(hash);
result -= ReadInt32BigEndian(header[0x1C..]) ^ ~ReadInt32BigEndian(hash[4..]);
return result;
}
protected override void SetChecksums() => ColoCrypto.SetChecksums(Data);
public override bool ChecksumsValid => !ChecksumInfo.Contains("Invalid");
public override string ChecksumInfo
{
get
{
var data = Data.AsSpan();
var header = data[..0x20];
var payload = data[..(SLOT_SIZE - (2 * sha1HashSize))];
var storedHash = data[^sha1HashSize..];
var hc = header[0x0C..];
int oldHC = ReadInt32BigEndian(hc);
// Clear Header Checksum
WriteInt32BigEndian(hc, 0);
Span<byte> currentHash = stackalloc byte[sha1HashSize];
SHA1.HashData(payload, currentHash);
// Compute new header checksum
int newHC = ComputeHeaderChecksum(header, currentHash);
// Restore old header checksum
WriteInt32BigEndian(hc, oldHC);
bool isHeaderValid = newHC == oldHC;
bool isBodyValid = storedHash.SequenceEqual(currentHash);
(bool isHeaderValid, bool isBodyValid) = ColoCrypto.IsChecksumValid(Data);
static string valid(bool s) => s ? "Valid" : "Invalid";
return $"Header Checksum {valid(isHeaderValid)}, Body Checksum {valid(isBodyValid)}.";
}
@ -281,14 +155,14 @@ public sealed class SAV3Colosseum : SaveFile, IGCSaveFile
return Box + (((30 * SIZE_STORED) + 0x14)*box) + 0x14;
}
private Span<byte> GetBoxNameSpan(int box) => Data.AsSpan(Box + (0x24A4 * box), 16);
private Span<byte> GetBoxNameSpan(int box) => Data.Slice(Box + (0x24A4 * box), 16);
public override string GetBoxName(int box)
public string GetBoxName(int box)
{
return GetString(GetBoxNameSpan(box));
}
public override void SetBoxName(int box, ReadOnlySpan<char> value)
public void SetBoxName(int box, ReadOnlySpan<char> value)
{
SetString(GetBoxNameSpan(box), value, 8, StringConverterOption.ClearZero);
}
@ -347,8 +221,8 @@ public sealed class SAV3Colosseum : SaveFile, IGCSaveFile
private TimeSpan PlayedSpan
{
get => TimeSpan.FromSeconds(ReadSingleBigEndian(Data.AsSpan(Config + 0x20)));
set => WriteSingleBigEndian(Data.AsSpan(Config + 0x20), (float)value.TotalSeconds);
get => TimeSpan.FromSeconds(ReadSingleBigEndian(Data[(Config + 0x20)..]));
set => WriteSingleBigEndian(Data[(Config + 0x20)..], (float)value.TotalSeconds);
}
public override int PlayedHours
@ -370,17 +244,17 @@ public sealed class SAV3Colosseum : SaveFile, IGCSaveFile
}
// Trainer Info (offset 0x78, length 0xB18, end @ 0xB90)
public override string OT { get => GetString(Data.AsSpan(0x78, 20)); set { SetString(Data.AsSpan(0x78, 20), value, 10, StringConverterOption.ClearZero); OT2 = value; } }
public string OT2 { get => GetString(Data.AsSpan(0x8C, 20)); set => SetString(Data.AsSpan(0x8C, 20), value, 10, StringConverterOption.ClearZero); }
public override string OT { get => GetString(Data.Slice(0x78, 20)); set { SetString(Data.Slice(0x78, 20), 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.AsSpan(0xA4)); set => WriteUInt32BigEndian(Data.AsSpan(0xA4), value); }
public override ushort SID16 { get => ReadUInt16BigEndian(Data.AsSpan(0xA4)); set => WriteUInt16BigEndian(Data.AsSpan(0xA4), value); }
public override ushort TID16 { get => ReadUInt16BigEndian(Data.AsSpan(0xA6)); set => WriteUInt16BigEndian(Data.AsSpan(0xA6), value); }
public override uint ID32 { get => ReadUInt32BigEndian(Data[0xA4..]); set => WriteUInt32BigEndian(Data[0xA4..], value); }
public override ushort SID16 { get => ReadUInt16BigEndian(Data[0xA4..]); set => WriteUInt16BigEndian(Data[0xA4..], value); }
public override ushort TID16 { get => ReadUInt16BigEndian(Data[0xA6..]); set => WriteUInt16BigEndian(Data[0xA6..], value); }
public override byte Gender { get => Data[0xAF8]; set => Data[0xAF8] = value; }
public override uint Money { get => ReadUInt32BigEndian(Data.AsSpan(0xAFC)); set => WriteUInt32BigEndian(Data.AsSpan(0xAFC), value); }
public uint Coupons { get => ReadUInt32BigEndian(Data.AsSpan(0xB00)); set => WriteUInt32BigEndian(Data.AsSpan(0xB00), value); }
public string RUI_Name { get => GetString(Data.AsSpan(0xB3A, 20)); set => SetString(Data.AsSpan(0xB3A, 20), value, 10, StringConverterOption.ClearZero); }
public override uint Money { get => ReadUInt32BigEndian(Data[0xAFC..]); set => WriteUInt32BigEndian(Data[0xAFC..], value); }
public uint Coupons { get => ReadUInt32BigEndian(Data[0xB00..]); set => WriteUInt32BigEndian(Data[0xB00..], value); }
public string RUI_Name { get => GetString(Data.Slice(0xB3A, 20)); set => SetString(Data.Slice(0xB3A, 20), value, 10, StringConverterOption.ClearZero); }
public override IReadOnlyList<InventoryPouch> Inventory
{
@ -406,11 +280,13 @@ public sealed class SAV3Colosseum : SaveFile, IGCSaveFile
// 0x01 -- Deposited Level
// 0x02-0x03 -- unused?
// 0x04-0x07 -- Initial EXP
public override int GetDaycareSlotOffset(int loc, int slot) { return DaycareOffset + 8; }
public override uint? GetDaycareEXP(int loc, int slot) { return null; }
public override bool? IsDaycareOccupied(int loc, int slot) { return null; }
public override void SetDaycareEXP(int loc, int slot, uint EXP) { }
public override void SetDaycareOccupied(int loc, int slot, bool occupied) { }
public int DaycareSlotCount => 1;
public bool IsDaycareOccupied(int slot) => Data[DaycareOffset] != 0;
public void SetDaycareOccupied(int slot, bool occupied) => Data[DaycareOffset] = (byte)(occupied ? 1 : 0);
public byte DaycareDepositLevel { get => Data[DaycareOffset + 1]; set => Data[DaycareOffset + 1] = value; }
public uint GetDaycareEXP(int index) => ReadUInt32BigEndian(Data[(DaycareOffset + 4)..]);
public void SetDaycareEXP(int index, uint value) => WriteUInt32BigEndian(Data[(DaycareOffset + 4)..], value);
public Memory<byte> GetDaycareSlot(int slot) => Raw.Slice(DaycareOffset + 8, PokeCrypto.SIZE_3CSTORED);
public override string GetString(ReadOnlySpan<byte> data) => StringConverter3GC.GetString(data);

View file

@ -8,7 +8,7 @@ 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
public sealed class SAV3E : SAV3, IGen3Hoenn, IGen3Joyful, IGen3Wonder, IDaycareRandomState<uint>
{
// Configuration
protected override SAV3E CloneInternal() => new(Write());
@ -18,7 +18,6 @@ public sealed class SAV3E : SAV3, IGen3Hoenn, IGen3Joyful, IGen3Wonder
public override int EventFlagCount => 8 * 300;
public override int EventWorkCount => 0x100;
protected override int DaycareSlotSize => SIZE_STORED + 0x3C; // 0x38 mail + 4 exp
public override int DaycareSeedSize => 8; // 32bit
protected override int EggEventFlag => 0x86;
protected override int BadgeFlagStart => 0x867;
@ -29,17 +28,11 @@ public sealed class SAV3E : SAV3, IGen3Hoenn, IGen3Joyful, IGen3Wonder
protected override int EventWork => 0x139C;
public override int MaxItemID => Legal.MaxItemID_3_E;
private void Initialize()
{
// small
PokeDex = 0x18;
protected override int PokeDex => 0x18; // small
protected override int DaycareOffset => 0x3030; // large
// large
DaycareOffset = 0x3030;
// storage
Box = 0;
}
// storage
private void Initialize() => Box = 0;
#region Small
public override bool NationalDex
@ -183,9 +176,12 @@ public sealed class SAV3E : SAV3, IGen3Hoenn, IGen3Joyful, IGen3Wonder
protected override int MailOffset => 0x2BE0;
protected override int GetDaycareEXPOffset(int slot) => GetDaycareSlotOffset(0, slot + 1) - 4; // @ end of each pk slot
public override string GetDaycareRNGSeed(int loc) => ReadUInt32LittleEndian(Large.AsSpan(GetDaycareSlotOffset(0, 2))).ToString("X8"); // after the 2 slots, before the step counter
public override void SetDaycareRNGSeed(int loc, string seed) => WriteUInt32LittleEndian(Large.AsSpan(GetDaycareEXPOffset(2)), Util.GetHexValue(seed));
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.AsSpan(GetDaycareEXPOffset(2)));
set => WriteUInt32LittleEndian(Large.AsSpan(GetDaycareEXPOffset(2)), value);
}
protected override int ExternalEventData => 0x31B3;
@ -224,12 +220,12 @@ public sealed class SAV3E : SAV3, IGen3Hoenn, IGen3Joyful, IGen3Wonder
/** Each value unit represents 1/60th of a second. Value 0 if no record. */
public uint GetTrainerHillRecord(TrainerHillMode3E mode)
{
return ReadUInt32LittleEndian(Large.AsSpan(OFS_TrainerHillRecord + (byte)mode * 4));
return ReadUInt32LittleEndian(Large.AsSpan(OFS_TrainerHillRecord + ((byte)mode * 4)));
}
public void SetTrainerHillRecord(TrainerHillMode3E mode, uint value)
{
WriteUInt32LittleEndian(Large.AsSpan(OFS_TrainerHillRecord + (byte)mode * 4), value);
WriteUInt32LittleEndian(Large.AsSpan(OFS_TrainerHillRecord + ((byte)mode * 4)), value);
State.Edited = true;
}

View file

@ -7,7 +7,7 @@ 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
public sealed class SAV3FRLG : SAV3, IGen3Joyful, IGen3Wonder, IDaycareRandomState<ushort>
{
// Configuration
protected override SAV3FRLG CloneInternal() => new(Write());
@ -18,7 +18,6 @@ public sealed class SAV3FRLG : SAV3, IGen3Joyful, IGen3Wonder
public override int EventFlagCount => 8 * 288;
public override int EventWorkCount => 0x100;
protected override int DaycareSlotSize => SIZE_STORED + 0x3C; // 0x38 mail + 4 exp
public override int DaycareSeedSize => 4; // 16bit
protected override int EggEventFlag => 0x266;
protected override int BadgeFlagStart => 0x820;
@ -36,18 +35,11 @@ public sealed class SAV3FRLG : SAV3, IGen3Joyful, IGen3Wonder
protected override int EventFlag => 0xEE0;
protected override int EventWork => 0x1000;
protected override int PokeDex => 0x18; // small
protected override int DaycareOffset => 0x2F80; // large
private void Initialize()
{
// small
PokeDex = 0x18;
// large
DaycareOffset = 0x2F80;
// storage
Box = 0;
}
// storage
private void Initialize() => Box = 0;
public bool ResetPersonal(GameVersion g)
{
@ -135,9 +127,12 @@ public sealed class SAV3FRLG : SAV3, IGen3Joyful, IGen3Wonder
protected override int SeenOffset2 => 0x5F8;
protected override int MailOffset => 0x2CD0;
protected override int GetDaycareEXPOffset(int slot) => GetDaycareSlotOffset(0, slot + 1) - 4; // @ end of each pk slot
public override string GetDaycareRNGSeed(int loc) => ReadUInt16LittleEndian(Large.AsSpan(GetDaycareEXPOffset(2))).ToString("X4"); // after the 2nd slot EXP, before the step counter
public override void SetDaycareRNGSeed(int loc, string seed) => WriteUInt16LittleEndian(Large.AsSpan(GetDaycareEXPOffset(2)), (ushort)Util.GetHexValue(seed));
protected override int GetDaycareEXPOffset(int slot) => GetDaycareSlotOffset(slot + 1) - 4; // @ end of each pk slot
ushort IDaycareRandomState<ushort>.Seed
{
get => ReadUInt16LittleEndian(Large.AsSpan(GetDaycareEXPOffset(2)));
set => WriteUInt16LittleEndian(Large.AsSpan(GetDaycareEXPOffset(2)), value);
}
protected override int ExternalEventData => 0x30A7;

View file

@ -201,11 +201,12 @@ public sealed class SAV3GCMemoryCard(byte[] Data)
public GCMemoryCardState GetMemoryCardState()
{
if (!IsMemoryCardSize(Data))
ReadOnlySpan<byte> data = Data;
if (!IsMemoryCardSize(data))
return GCMemoryCardState.Invalid; // Invalid size
// Size in megabits, not megabytes
int m_sizeMb = Data.Length / BLOCK_SIZE / MBIT_TO_BLOCKS;
int m_sizeMb = data.Length / BLOCK_SIZE / MBIT_TO_BLOCKS;
if (m_sizeMb != Header_Size) // Memory card file size does not match the header size
return GCMemoryCardState.Invalid;
@ -222,18 +223,18 @@ public sealed class SAV3GCMemoryCard(byte[] Data)
for (int i = 0; i < NumEntries_Directory; i++)
{
int offset = (DirectoryBlock_Used * BLOCK_SIZE) + (i * DENTRY_SIZE);
if (ReadUInt32BigEndian(Data.AsSpan(offset)) == uint.MaxValue) // empty entry
if (ReadUInt32BigEndian(data[offset..]) == uint.MaxValue) // empty entry
continue;
int FirstBlock = ReadUInt16BigEndian(Data.AsSpan(offset + 0x36));
int FirstBlock = ReadUInt16BigEndian(data[0x36..]);
if (FirstBlock < 5)
continue;
int BlockCount = ReadUInt16BigEndian(Data.AsSpan(offset + 0x38));
int BlockCount = ReadUInt16BigEndian(data[0x38..]);
var dataEnd = (FirstBlock + BlockCount) * BLOCK_SIZE;
// Memory card directory contains info for deleted files with boundaries beyond memory card size, ignore
if (dataEnd > Data.Length)
if (dataEnd > data.Length)
continue;
var header = Data.AsSpan(offset, 4);

View file

@ -8,7 +8,7 @@ namespace PKHeX.Core;
/// Generation 3 <see cref="SaveFile"/> object for <see cref="GameVersion.RS"/>.
/// </summary>
/// <inheritdoc cref="SAV3" />
public sealed class SAV3RS : SAV3, IGen3Hoenn
public sealed class SAV3RS : SAV3, IGen3Hoenn, IDaycareRandomState<ushort>
{
// Configuration
protected override SAV3RS CloneInternal() => new(Write());
@ -18,7 +18,6 @@ public sealed class SAV3RS : SAV3, IGen3Hoenn
public override int EventFlagCount => 8 * 288;
public override int EventWorkCount => 0x100;
protected override int DaycareSlotSize => SIZE_STORED;
public override int DaycareSeedSize => 4; // 16bit
protected override int EggEventFlag => 0x86;
protected override int BadgeFlagStart => 0x807;
@ -28,17 +27,11 @@ public sealed class SAV3RS : SAV3, IGen3Hoenn
protected override int EventFlag => 0x1220;
protected override int EventWork => 0x1340;
private void Initialize()
{
// small
PokeDex = 0x18;
protected override int PokeDex => 0x18; // small
protected override int DaycareOffset => 0x2F9C; // large
// large
DaycareOffset = 0x2F9C;
// storage
Box = 0;
}
// storage
private void Initialize() => Box = 0;
#region Small
public override bool NationalDex
@ -142,9 +135,12 @@ public sealed class SAV3RS : SAV3, IGen3Hoenn
protected override int MailOffset => 0x2B4C;
protected override int GetDaycareEXPOffset(int slot) => GetDaycareSlotOffset(0, 2) + (2 * 0x38) + (4 * slot); // consecutive vals, after both consecutive slots & 2 mail
public override string GetDaycareRNGSeed(int loc) => ReadUInt16LittleEndian(Large.AsSpan(GetDaycareEXPOffset(2))).ToString("X4");
public override void SetDaycareRNGSeed(int loc, string seed) => WriteUInt16LittleEndian(Large.AsSpan(GetDaycareEXPOffset(2)), (ushort)Util.GetHexValue(seed));
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.AsSpan(GetDaycareEXPOffset(2)));
set => WriteUInt16LittleEndian(Large.AsSpan(GetDaycareEXPOffset(2)), value);
}
protected override int ExternalEventData => 0x311B;

View file

@ -7,7 +7,7 @@ namespace PKHeX.Core;
/// <summary>
/// Generation 3 <see cref="SaveFile"/> object for Pokémon Ruby Sapphire Box saves.
/// </summary>
public sealed class SAV3RSBox : SaveFile, IGCSaveFile
public sealed class SAV3RSBox : SaveFile, IGCSaveFile, IBoxDetailName, IBoxDetailWallpaper
{
protected internal override string ShortSummary => $"{Version} #{SaveCount:0000}";
public override string Extension => this.GCExtension();
@ -115,11 +115,9 @@ public sealed class SAV3RSBox : SaveFile, IGCSaveFile
public override int MaxEV => EffortValues.Max255;
public override byte Generation => 3;
public override EntityContext Context => EntityContext.Gen3;
protected override int GiftCountMax => 1;
public override int MaxStringLengthOT => 7;
public override int MaxStringLengthNickname => 10;
public override int MaxMoney => 999999;
public override bool HasBoxWallpapers => false;
public override int BoxCount => 50;
public override bool HasParty => false;
@ -149,13 +147,16 @@ public sealed class SAV3RSBox : SaveFile, IGCSaveFile
return Data.AsSpan(offset, 9);
}
protected override int GetBoxWallpaperOffset(int box)
private int GetBoxWallpaperOffset(int box)
{
// Box Wallpaper is directly after the Box Names
return Box + 0x1ED19 + (box / 2);
}
public override string GetBoxName(int box)
public int GetBoxWallpaper(int box) => Data[GetBoxWallpaperOffset(box)];
public void SetBoxWallpaper(int box, int value) => Data[GetBoxWallpaperOffset(box)] = (byte)value;
public string GetBoxName(int box)
{
// Tweaked for the 1-30/31-60 box showing
int lo = (30 *(box%2)) + 1;
@ -165,17 +166,17 @@ public sealed class SAV3RSBox : SaveFile, IGCSaveFile
var span = GetBoxNameSpan(box);
if (span[0] is 0 or 0xFF)
boxName += $"BOX {box + 1}";
boxName += BoxDetailNameExtensions.GetDefaultBoxNameCaps(box);
else
boxName += GetString(span);
return boxName;
}
public override void SetBoxName(int box, ReadOnlySpan<char> value)
public void SetBoxName(int box, ReadOnlySpan<char> value)
{
var span = GetBoxNameSpan(box);
if (value == $"BOX {box + 1}")
if (value == BoxDetailNameExtensions.GetDefaultBoxNameCaps(box))
{
span.Clear();
return;

View file

@ -7,7 +7,7 @@ namespace PKHeX.Core;
/// <summary>
/// Generation 3 <see cref="SaveFile"/> object for Pokémon XD saves.
/// </summary>
public sealed class SAV3XD : SaveFile, IGCSaveFile
public sealed class SAV3XD : SaveFile, IGCSaveFile, IBoxDetailName, IDaycareStorage, IDaycareExperience
{
protected internal override string ShortSummary => $"{OT} ({Version}) {PlayTimeString}";
public override string Extension => this.GCExtension();
@ -29,6 +29,7 @@ public sealed class SAV3XD : SaveFile, IGCSaveFile
private int OFS_PouchHeldItem, OFS_PouchKeyItem, OFS_PouchBalls, OFS_PouchTMHM, OFS_PouchBerry, OFS_PouchCologne, OFS_PouchDisc;
private readonly int[] subOffsets = new int[16];
private readonly byte[] BAK;
private int DaycareOffset;
public SAV3XD() : base(SaveUtil.SIZE_G3XD)
{
@ -191,7 +192,6 @@ public sealed class SAV3XD : SaveFile, IGCSaveFile
public override int MaxEV => EffortValues.Max255;
public override byte Generation => 3;
public override EntityContext Context => EntityContext.Gen3;
protected override int GiftCountMax => 1;
public override int MaxStringLengthOT => 7;
public override int MaxStringLengthNickname => 10;
public override int MaxMoney => 9999999;
@ -334,9 +334,9 @@ public sealed class SAV3XD : SaveFile, IGCSaveFile
public override int GetPartyOffset(int slot) => Party + (SIZE_STORED * slot);
private int GetBoxInfoOffset(int box) => Box + (((30 * SIZE_STORED) + 0x14) * box);
public override int GetBoxOffset(int box) => GetBoxInfoOffset(box) + 20;
public override string GetBoxName(int box) => GetString(Data.AsSpan(GetBoxInfoOffset(box), 16));
public string GetBoxName(int box) => GetString(Data.AsSpan(GetBoxInfoOffset(box), 16));
public override void SetBoxName(int box, ReadOnlySpan<char> value)
public void SetBoxName(int box, ReadOnlySpan<char> value)
{
SetString(Data.AsSpan(GetBoxInfoOffset(box), 20), value, 8, StringConverterOption.ClearZero);
}
@ -444,11 +444,13 @@ public sealed class SAV3XD : SaveFile, IGCSaveFile
// 0x01 -- Deposited Level
// 0x02-0x03 -- unused?
// 0x04-0x07 -- Initial EXP
public override int GetDaycareSlotOffset(int loc, int slot) { return DaycareOffset + 8; }
public override uint? GetDaycareEXP(int loc, int slot) { return null; }
public override bool? IsDaycareOccupied(int loc, int slot) { return null; }
public override void SetDaycareEXP(int loc, int slot, uint EXP) { /* todo */ }
public override void SetDaycareOccupied(int loc, int slot, bool occupied) { /* todo */ }
public int DaycareSlotCount => 1;
public bool IsDaycareOccupied(int slot) => Data[DaycareOffset] != 0;
public void SetDaycareOccupied(int slot, bool occupied) => Data[DaycareOffset] = (byte)(occupied ? 1 : 0);
public byte DaycareDepositLevel { get => Data[DaycareOffset + 1]; set => Data[DaycareOffset + 1] = value; }
public uint GetDaycareEXP(int index) => ReadUInt32BigEndian(Data.AsSpan(DaycareOffset + 4));
public void SetDaycareEXP(int index, uint value) => WriteUInt32BigEndian(Data.AsSpan(DaycareOffset + 4), value);
public Memory<byte> GetDaycareSlot(int slot) => Data.AsMemory(DaycareOffset + 8, PokeCrypto.SIZE_3XSTORED);
public override string GetString(ReadOnlySpan<byte> data) => StringConverter3GC.GetString(data);

View file

@ -11,7 +11,7 @@ namespace PKHeX.Core;
/// <remarks>
/// Storage data is stored in one contiguous block, and the remaining data is stored in another block.
/// </remarks>
public abstract class SAV4 : SaveFile, IEventFlag37
public abstract class SAV4 : SaveFile, IEventFlag37, IDaycareStorage, IDaycareRandomState<uint>, IDaycareExperience, IDaycareEggState, IMysteryGiftStorageProvider
{
protected internal override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}";
public sealed override string Extension => ".sav";
@ -37,8 +37,8 @@ public abstract class SAV4 : SaveFile, IEventFlag37
protected abstract int EventFlag { get; }
protected abstract int EventWork { get; }
public sealed override bool GetFlag(int offset, int bitIndex) => FlagUtil.GetFlag(General, offset, bitIndex);
public sealed override void SetFlag(int offset, int bitIndex, bool value) => FlagUtil.SetFlag(General, offset, bitIndex, value);
public sealed override bool GetFlag(int offset, int bitIndex) => GetFlag(General, offset, bitIndex);
public sealed override void SetFlag(int offset, int bitIndex, bool value) => SetFlag(General, offset, bitIndex, value);
protected SAV4([ConstantExpected] int gSize, [ConstantExpected] int sSize)
{
@ -95,7 +95,6 @@ public abstract class SAV4 : SaveFile, IEventFlag37
public override EntityContext Context => EntityContext.Gen4;
public int EventFlagCount => 0xB60; // 2912
public int EventWorkCount => (EventFlag - EventWork) >> 1;
protected sealed override int GiftCountMax => 11;
public sealed override int MaxStringLengthOT => 7;
public sealed override int MaxStringLengthNickname => 10;
public sealed override int MaxMoney => 999999;
@ -213,7 +212,7 @@ public abstract class SAV4 : SaveFile, IEventFlag37
public int GTS { get; protected set; } = int.MinValue;
public int ChatterOffset { get; protected set; } = int.MinValue;
public Chatter4 Chatter => new(this, ChatterOffset);
public Chatter4 Chatter => new(this, Data.AsMemory(ChatterOffset));
// Storage
public override int PartyCount
@ -352,172 +351,57 @@ public abstract class SAV4 : SaveFile, IEventFlag37
}
// Daycare
public override int GetDaycareSlotOffset(int loc, int slot) => DaycareOffset + (slot * SIZE_PARTY);
public int DaycareSlotCount => 2;
private const int DaycareSlotSize = PokeCrypto.SIZE_4PARTY;
protected abstract int DaycareOffset { get; }
public Memory<byte> GetDaycareSlot(int slot) => GeneralBuffer.Slice(DaycareOffset + (slot * DaycareSlotSize), DaycareSlotSize);
public override uint? GetDaycareEXP(int loc, int slot)
// EXP: Last 4 bytes of each slot
public uint GetDaycareEXP(int index)
{
int ofs = DaycareOffset + ((slot+1)*SIZE_PARTY) - 4;
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, 2u);
int ofs = DaycareOffset + DaycareSlotSize - 4;
return ReadUInt32LittleEndian(General[ofs..]);
}
public override bool? IsDaycareOccupied(int loc, int slot) => null; // todo
public override void SetDaycareEXP(int loc, int slot, uint EXP)
public void SetDaycareEXP(int index, uint value)
{
int ofs = DaycareOffset + ((slot+1)*SIZE_PARTY) - 4;
WriteUInt32LittleEndian(General[ofs..], EXP);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, 2u);
int ofs = DaycareOffset + DaycareSlotSize - 4;
WriteUInt32LittleEndian(General[ofs..], value);
}
public override void SetDaycareOccupied(int loc, int slot, bool occupied)
public bool IsDaycareOccupied(int index) => GetStoredSlot(GetDaycareSlot(index).Span).Species != 0;
public void SetDaycareOccupied(int index, bool occupied)
{
// todo
if (occupied)
return; // how would we even set this? just ignore and assume they'll set the slot data via other means.
GetDaycareSlot(index).Span.Clear();
}
uint IDaycareRandomState<uint>.Seed
{
get => ReadUInt32LittleEndian(General[(DaycareOffset + (2 * DaycareSlotSize))..]);
set => WriteUInt32LittleEndian(General[DaycareOffset..], value);
}
public byte DaycareStepCounter { get => General[DaycareOffset + (2 * DaycareSlotSize) + 4]; set => General[DaycareOffset + (2 * DaycareSlotSize) + 4] = value; }
public bool IsEggAvailable
{
get => ((IDaycareRandomState<uint>)this).Seed != 0;
set
{
if (!value)
((IDaycareRandomState<uint>)this).Seed = 0;
else if (((IDaycareRandomState<uint>)this).Seed == 0)
((IDaycareRandomState<uint>)this).Seed = (uint)Util.Rand.Next(1, int.MaxValue);
}
}
// Mystery Gift
private bool MysteryGiftActive { get => (General[72] & 1) == 1; set => General[72] = (byte)((General[72] & 0xFE) | (value ? 1 : 0)); }
private static bool IsMysteryGiftAvailable(DataMysteryGift[] value)
{
for (int i = 0; i < 8; i++) // 8 PGT
{
if (value[i] is PGT {CardType: not 0})
return true;
}
for (int i = 8; i < 11; i++) // 3 PCD
{
if (value[i] is PCD {Gift.CardType: not 0 })
return true;
}
return false;
}
private bool MatchMysteryGifts(DataMysteryGift[] value, Span<byte> indexes)
{
for (int i = 0; i < 8; i++)
{
if (value[i] is not PGT pgt)
continue;
if (pgt.CardType == 0) // empty
{
indexes[i] = pgt.Slot = 0;
continue;
}
indexes[i] = pgt.Slot = 3;
for (byte j = 0; j < 3; j++)
{
if (value[8 + j] is not PCD pcd)
continue;
// Check if data matches (except Slot @ 0x02)
if (!pcd.GiftEquals(pgt))
continue;
if (this is not SAV4HGSS)
j++; // HG/SS 0,1,2; D/P/Pt 1,2,3
indexes[i] = pgt.Slot = j;
break;
}
}
return true;
}
public override MysteryGiftAlbum GiftAlbum
{
get => new(MysteryGiftCards, MysteryGiftReceivedFlags) {Flags = {[2047] = false}};
set
{
bool available = IsMysteryGiftAvailable(value.Gifts);
if (available && !MysteryGiftActive)
MysteryGiftActive = true;
value.Flags[2047] = available;
// Check encryption for each gift (decrypted wc4 sneaking in)
foreach (var g in value.Gifts)
{
if (g is PGT pgt)
{
pgt.VerifyPKEncryption();
}
else if (g is PCD pcd)
{
var dg = pcd.Gift;
if (dg.VerifyPKEncryption())
pcd.Gift = dg; // set encrypted gift back to PCD.
}
}
MysteryGiftReceivedFlags = value.Flags;
MysteryGiftCards = value.Gifts;
}
}
protected sealed override bool[] MysteryGiftReceivedFlags
{
get
{
bool[] result = new bool[GiftFlagMax];
for (int i = 0; i < result.Length; i++)
result[i] = ((General[WondercardFlags + (i >> 3)] >> (i & 7)) & 0x1) == 1;
return result;
}
set
{
if (GiftFlagMax != value.Length)
return;
Span<byte> data = General.Slice(WondercardFlags, value.Length / 8);
data.Clear();
for (int i = 0; i < value.Length; i++)
{
if (value[i])
data[i >> 3] |= (byte)(1 << (i & 7));
}
}
}
protected sealed override DataMysteryGift[] MysteryGiftCards
{
get
{
int pcd = this is SAV4HGSS ? 4 : 3;
DataMysteryGift[] cards = new DataMysteryGift[8 + pcd];
for (int i = 0; i < 8; i++) // 8 PGT
cards[i] = new PGT(General.Slice(WondercardData + (i * PGT.Size), PGT.Size).ToArray());
for (int i = 8; i < 11; i++) // 3 PCD
cards[i] = new PCD(General.Slice(WondercardData + (8 * PGT.Size) + ((i-8) * PCD.Size), PCD.Size).ToArray());
if (this is SAV4HGSS hgss)
cards[^1] = hgss.LockCapsuleSlot;
return cards;
}
set
{
Span<byte> indexes = stackalloc byte[8];
bool matchAny = MatchMysteryGifts(value, indexes); // automatically applied
if (!matchAny)
return;
for (int i = 0; i < 8; i++) // 8 PGT
{
if (value[i] is PGT)
{
var ofs = (WondercardData + (i * PGT.Size));
SetData(General[ofs..], value[i].Data);
}
}
for (int i = 8; i < 11; i++) // 3 PCD
{
if (value[i] is PCD)
{
var ofs = (WondercardData + (8 * PGT.Size) + ((i - 8) * PCD.Size));
SetData(General[ofs..], value[i].Data);
}
}
if (this is SAV4HGSS hgss && value.Length >= 11 && value[^1] is PCD capsule)
hgss.LockCapsuleSlot = capsule;
}
}
public bool IsMysteryGiftUnlocked { get => (General[72] & 1) == 1; set => General[72] = (byte)((General[72] & 0xFE) | (value ? 1 : 0)); }
protected sealed override void SetDex(PKM pk) => Dex.SetDex(pk);
public sealed override bool GetCaught(ushort species) => Dex.GetCaught(species);
@ -654,4 +538,209 @@ public abstract class SAV4 : SaveFile, IEventFlag37
public abstract int BP { get; set; }
public abstract BattleFrontierFacility4 MaxFacility { get; }
public abstract MysteryBlock4 Mystery { get; }
IMysteryGiftStorage IMysteryGiftStorageProvider.MysteryGiftStorage => Mystery;
}
public sealed class MysteryBlock4DP(SAV4DP sav, Memory<byte> raw) : MysteryBlock4(sav, raw)
{
// 0x100 Flags
// 11 u32 IsActive sentinels
// 8 PGT
// 3 PCD
public const int Size = (MaxReceivedFlag / 8) + (SentinelCount * sizeof(uint)) + (MaxCountPGT * PGT.Size) + (MaxCountPCD * PCD.Size);
protected override int CardStart => FlagStart + FlagRegionSize + (11 * sizeof(uint));
private const int SentinelCount = 11;
// reverse crc32 polynomial, nice!
private const uint MysteryGiftDPSlotActive = 0xEDB88320;
private Span<byte> SentinelSpan => Data.Slice(FlagStart + FlagRegionSize, 11 * sizeof(uint));
public uint GetMysteryGiftReceivedSentinel(int index)
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual<uint>((uint)index, SentinelCount);
return ReadUInt32LittleEndian(SentinelSpan[(index * sizeof(uint))..]);
}
public void SetMysteryGiftReceivedSentinel(int index, uint value)
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual<uint>((uint)index, SentinelCount);
WriteUInt32LittleEndian(SentinelSpan[(index * sizeof(uint))..], value);
}
public override void SetMysteryGift(int index, PGT pgt)
{
base.SetMysteryGift(index, pgt);
SetMysteryGiftReceivedSentinel(index, pgt.Empty ? 0 : MysteryGiftDPSlotActive);
}
public override void SetMysteryGift(int index, PCD pcd)
{
base.SetMysteryGift(index, pcd);
SetMysteryGiftReceivedSentinel(index, pcd.Empty ? 0 : MysteryGiftDPSlotActive);
}
}
public sealed class MysteryBlock4Pt(SAV4Pt sav, Memory<byte> raw) : MysteryBlock4(sav, raw)
{
// 0x100 Flags
// 8 PGT
// 3 PCD
public const int Size = (MaxReceivedFlag / 8) + (MaxCountPGT * PGT.Size) + (MaxCountPCD * PCD.Size);
protected override int CardStart => FlagStart + FlagRegionSize;
}
public sealed class MysteryBlock4HGSS(SAV4HGSS sav, Memory<byte> raw) : MysteryBlock4(sav, raw)
{
// 0x100 Flags
// 8 PGT
// 3 PCD0x100
public const int Size = (MaxReceivedFlag / 8) + (MaxCountPGT * PGT.Size) + (MaxCountPCD * PCD.Size);
protected override int CardStart => FlagStart + FlagRegionSize;
}
public abstract class MysteryBlock4(SAV4 sav, Memory<byte> raw) : SaveBlock<SAV4>(sav, raw), IMysteryGiftStorage, IMysteryGiftFlags
{
protected const int FlagStart = 0;
protected const int MaxReceivedFlag = 2048;
protected const int MaxCountPGT = 8;
protected const int MaxCountPCD = 3;
protected const int MaxCardsPresent = MaxCountPGT + MaxCountPCD;
protected const int FlagRegionSize = (MaxReceivedFlag / 8); // 0x100
protected abstract int CardStart { get; }
private const int FlagDeliveryManActive = 2047;
private int CardRegionPGTStart => CardStart;
private int CardRegionPCDStart => CardRegionPGTStart + (MaxCountPGT * PGT.Size);
public bool IsDeliveryManActive
{
get => GetMysteryGiftReceivedFlag(FlagDeliveryManActive);
set
{
if (value && !SAV.IsMysteryGiftUnlocked)
SAV.IsMysteryGiftUnlocked = true; // be nice to the user and unlock the Mystery Gift menu feature.
SetMysteryGiftReceivedFlag(FlagDeliveryManActive, value);
}
}
public int GiftCountMax => MaxCardsPresent;
DataMysteryGift IMysteryGiftStorage.GetMysteryGift(int index)
{
if ((uint)index < MaxCountPGT)
return GetMysteryGiftPGT(index);
if ((uint)index < MaxCardsPresent)
return GetMysteryGiftPCD(index - MaxCountPGT);
throw new ArgumentOutOfRangeException(nameof(index));
}
void IMysteryGiftStorage.SetMysteryGift(int index, DataMysteryGift gift)
{
if ((uint) index < MaxCountPGT)
SetMysteryGift(index, (PGT)gift);
else if ((uint)index < MaxCardsPresent)
SetMysteryGift(index - MaxCountPGT, (PCD)gift);
else throw new ArgumentOutOfRangeException(nameof(index));
}
public int MysteryGiftReceivedFlagMax => FlagDeliveryManActive; // ignore the delivery man flag when populating flags
private Span<byte> FlagRegion => Data[..CardStart]; // 0x100
public void ClearReceivedFlags() => FlagRegion.Clear();
private int GetGiftOffsetPGT(int index)
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, (uint)MaxCountPGT);
return CardRegionPGTStart + (index * PGT.Size);
}
private int GetGiftOffsetPCD(int index)
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, (uint)MaxCountPCD);
return CardRegionPCDStart + (index * PCD.Size);
}
private Span<byte> GetCardSpanPGT(int index) => Data.Slice(GetGiftOffsetPGT(index), PGT.Size);
private Span<byte> GetCardSpanPCD(int index) => Data.Slice(GetGiftOffsetPCD(index), PGT.Size);
public PGT GetMysteryGiftPGT(int index) => new(GetCardSpanPGT(index).ToArray());
public PCD GetMysteryGiftPCD(int index) => new(GetCardSpanPCD(index).ToArray());
public virtual void SetMysteryGift(int index, PGT pgt)
{
if ((uint)index > MaxCardsPresent)
throw new ArgumentOutOfRangeException(nameof(index));
if (pgt.Data.Length != PGT.Size)
throw new InvalidCastException(nameof(pgt));
pgt.VerifyPKEncryption();
SAV.SetData(GetCardSpanPGT(index), pgt.Data);
}
public virtual void SetMysteryGift(int index, PCD pcd)
{
if ((uint)index > MaxCardsPresent)
throw new ArgumentOutOfRangeException(nameof(index));
if (pcd.Data.Length != PCD.Size)
throw new InvalidCastException(nameof(pcd));
var gift = pcd.Gift;
if (gift.VerifyPKEncryption())
pcd.Gift = gift; // ensure data is encrypted in the object
SAV.SetData(GetCardSpanPCD(index), pcd.Data);
}
public bool GetMysteryGiftReceivedFlag(int index)
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, (uint)MaxReceivedFlag);
return FlagUtil.GetFlag(Data, index); // offset 0
}
public void SetMysteryGiftReceivedFlag(int index, bool value)
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, (uint)MaxReceivedFlag);
FlagUtil.SetFlag(Data, index, value); // offset 0
}
// HG/SS 0,1,2,[3]; D/P/Pt 1,2,3,[4]
private const byte NoPCDforPGT = 3; // 4 for DPPt; handle this manually.
/// <summary>
/// Each PGT points to a PCD slot if it is correlated.
/// </summary>
public static void UpdateSlotPGT(ReadOnlySpan<DataMysteryGift> value, bool hgss)
{
var arrPGT = value[..MaxCountPGT];
var arrPCD = value.Slice(MaxCountPGT, MaxCountPCD);
UpdateSlotPGT(hgss, arrPGT, arrPCD);
}
public static void UpdateSlotPGT(bool hgss, ReadOnlySpan<DataMysteryGift> arrPGT, ReadOnlySpan<DataMysteryGift> arrPCD)
{
foreach (var gift in arrPGT)
{
var pgt = (PGT)gift;
if (pgt.CardType == 0) // empty
{
pgt.Slot = 0;
continue;
}
var index = FindIndexPCD(pgt, arrPCD);
if (!hgss)
index++;
pgt.Slot = index;
}
}
private static byte FindIndexPCD(PGT pgt, ReadOnlySpan<DataMysteryGift> arrPCD)
{
for (byte i = 0; i < arrPCD.Length; i++)
{
var pcd = (PCD)arrPCD[i];
// Check if data matches (except Slot @ 0x02)
if (pcd.GiftEquals(pgt))
return i;
}
return NoPCDforPGT;
}
}

View file

@ -7,7 +7,7 @@ namespace PKHeX.Core;
/// <summary>
/// Generation 4 <see cref="SaveFile"/> object for Pokémon Battle Revolution saves.
/// </summary>
public sealed class SAV4BR : SaveFile
public sealed class SAV4BR : SaveFile, IBoxDetailName
{
protected internal override string ShortSummary => $"{Version} #{SaveCount:0000}";
public override string Extension => string.Empty;
@ -107,7 +107,6 @@ public sealed class SAV4BR : SaveFile
public override int MaxEV => EffortValues.Max255;
public override byte Generation => 4;
public override EntityContext Context => EntityContext.Gen4;
protected override int GiftCountMax => 1;
public override int MaxStringLengthOT => 7;
public override int MaxStringLengthNickname => 10;
public override int MaxMoney => 999999;
@ -206,18 +205,18 @@ public sealed class SAV4BR : SaveFile
return Data.AsSpan(ofs, BoxNameLength);
}
public override string GetBoxName(int box)
public string GetBoxName(int box)
{
if (BoxName < 0)
return $"BOX {box + 1}";
return BoxDetailNameExtensions.GetDefaultBoxNameCaps(box);
var span = GetBoxNameSpan(box);
if (ReadUInt16BigEndian(span) == 0)
return $"BOX {box + 1}";
return BoxDetailNameExtensions.GetDefaultBoxNameCaps(box);
return GetString(span);
}
public override void SetBoxName(int box, ReadOnlySpan<char> value)
public void SetBoxName(int box, ReadOnlySpan<char> value)
{
if (BoxName < 0)
return;

View file

@ -12,16 +12,21 @@ public sealed class SAV4DP : SAV4Sinnoh
public SAV4DP() : base(GeneralSize, StorageSize)
{
Initialize();
Dex = new Zukan4(this, PokeDex);
Mystery = new MysteryBlock4DP(this, GeneralBuffer.Slice(OffsetMystery, MysteryBlock4DP.Size));
Dex = new Zukan4(this, GeneralBuffer[PokeDex..]);
}
public SAV4DP(byte[] data) : base(data, GeneralSize, StorageSize, GeneralSize)
{
Initialize();
Dex = new Zukan4(this, PokeDex);
Mystery = new MysteryBlock4DP(this, GeneralBuffer.Slice(OffsetMystery, MysteryBlock4DP.Size));
Dex = new Zukan4(this, GeneralBuffer[PokeDex..]);
}
private const int OffsetMystery = 0xA7FC;
private const int PokeDex = 0x12DC;
public override Zukan4 Dex { get; }
public override MysteryBlock4DP Mystery { get; }
protected override SAV4 CloneInternal4() => State.Exportable ? new SAV4DP((byte[])Data.Clone()) : new SAV4DP();
public override GameVersion Version { get => GameVersion.DP; set { } }
@ -45,6 +50,7 @@ public sealed class SAV4DP : SAV4Sinnoh
protected override int EventWork => 0xD9C;
protected override int EventFlag => 0xFDC;
protected override int DaycareOffset => 0x141C;
public override BattleFrontierFacility4 MaxFacility => BattleFrontierFacility4.Tower;
private void GetSAVOffsets()
@ -52,15 +58,10 @@ public sealed class SAV4DP : SAV4Sinnoh
AdventureInfo = 0;
Trainer1 = 0x64;
Party = 0x98;
PokeDex = 0x12DC;
ChatterOffset = 0x61CC;
Geonet = 0x96D8;
WondercardFlags = 0xA6D0;
WondercardData = 0xA7fC;
DaycareOffset = 0x141C;
OFS_HONEY = 0x72E4;
OFS_UG_Stats = 0x3A2C;
OFS_UG_Items = 0x42B0;
@ -109,47 +110,6 @@ public sealed class SAV4DP : SAV4Sinnoh
set => value.SaveAll(General);
}
// reverse crc32 polynomial, nice!
private const uint MysteryGiftDPSlotActive = 0xEDB88320;
public bool[] GetMysteryGiftDPSlotActiveFlags()
{
var span = General[(WondercardFlags + 0x100)..]; // skip over flags
bool[] active = new bool[GiftCountMax]; // 8 PGT, 3 PCD
for (int i = 0; i < active.Length; i++)
active[i] = ReadUInt32LittleEndian(span[(4*i)..]) == MysteryGiftDPSlotActive;
return active;
}
public void SetMysteryGiftDPSlotActiveFlags(ReadOnlySpan<bool> value)
{
if (value.Length != GiftCountMax)
return;
var span = General[(WondercardFlags + 0x100)..]; // skip over flags
for (int i = 0; i < value.Length; i++)
WriteUInt32LittleEndian(span[(4 * i)..], value[i] ? MysteryGiftDPSlotActive : 0);
}
public override MysteryGiftAlbum GiftAlbum
{
get => base.GiftAlbum;
set
{
base.GiftAlbum = value;
SetActiveGiftFlags(value.Gifts);
}
}
private void SetActiveGiftFlags(IReadOnlyList<MysteryGift> gifts)
{
var arr = new bool[gifts.Count];
for (int i = 0; i < arr.Length; i++)
arr[i] = !gifts[i].Empty;
SetMysteryGiftDPSlotActiveFlags(arr);
}
public override int M { get => ReadUInt16LittleEndian(General[0x1238..]); set => WriteUInt16LittleEndian(General[0x1238..], (ushort)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)); }

View file

@ -8,18 +8,20 @@ namespace PKHeX.Core;
/// <summary>
/// <see cref="SaveFile"/> format for <see cref="GameVersion.HGSS"/>
/// </summary>
public sealed class SAV4HGSS : SAV4
public sealed class SAV4HGSS : SAV4, IBoxDetailName, IBoxDetailWallpaper
{
public SAV4HGSS() : base(GeneralSize, StorageSize)
{
Initialize();
Dex = new Zukan4(this, PokeDex);
Mystery = new MysteryBlock4HGSS(this, GeneralBuffer.Slice(OffsetMystery, MysteryBlock4HGSS.Size));
Dex = new Zukan4(this, GeneralBuffer[PokeDex..]);
}
public SAV4HGSS(byte[] data) : base(data, GeneralSize, StorageSize, GeneralSize + GeneralGap)
{
Initialize();
Dex = new Zukan4(this, PokeDex);
Mystery = new MysteryBlock4HGSS(this, GeneralBuffer.Slice(OffsetMystery, MysteryBlock4HGSS.Size));
Dex = new Zukan4(this, GeneralBuffer[PokeDex..]);
}
public override Zukan4 Dex { get; }
@ -32,6 +34,7 @@ public sealed class SAV4HGSS : SAV4
public const int GeneralSize = 0xF628;
private const int StorageSize = 0x12310; // Start 0xF700, +0 starts box data
private const int GeneralGap = 0xD8;
private const int PokeDex = 0x12B8;
protected override int FooterSize => 0x10;
protected override BlockInfo4[] ExtraBlocks =>
@ -50,8 +53,11 @@ public sealed class SAV4HGSS : SAV4
GetSAVOffsets();
}
private const int OffsetMystery = 0x9E3C;
protected override int EventWork => 0xDE4;
protected override int EventFlag => 0x10C4;
protected override int DaycareOffset => 0x15FC;
public override MysteryBlock4HGSS Mystery { get; }
public override BattleFrontierFacility4 MaxFacility => BattleFrontierFacility4.Arcade;
private void GetSAVOffsets()
@ -59,14 +65,10 @@ public sealed class SAV4HGSS : SAV4
AdventureInfo = 0;
Trainer1 = 0x64;
Party = 0x98;
PokeDex = 0x12B8;
Extra = 0x230C;
ChatterOffset = 0x4E74;
Geonet = 0x8D44;
WondercardFlags = 0x9D3C;
WondercardData = 0x9E3C;
DaycareOffset = 0x15FC;
Seal = 0x4E20;
Box = 0;
@ -106,7 +108,7 @@ public sealed class SAV4HGSS : SAV4
public override int GetBoxOffset(int box) => box * 0x1000;
private static int GetBoxNameOffset(int box) => BOX_NAME + (box * BOX_NAME_LEN);
protected override int GetBoxWallpaperOffset(int box) => BOX_WP + box;
private static int GetBoxWallpaperOffset(int box) => BOX_WP + box;
// 8 bytes current box (align 32) & (stored count?)
public override int CurrentBox
@ -128,9 +130,9 @@ public sealed class SAV4HGSS : SAV4
}
private Span<byte> GetBoxNameSpan(int box) => Storage.Slice(GetBoxNameOffset(box), BOX_NAME_LEN);
public override string GetBoxName(int box) => GetString(GetBoxNameSpan(box));
public string GetBoxName(int box) => GetString(GetBoxNameSpan(box));
public override void SetBoxName(int box, ReadOnlySpan<char> value)
public void SetBoxName(int box, ReadOnlySpan<char> value)
{
const int maxlen = 8;
var span = GetBoxNameSpan(box);
@ -146,14 +148,14 @@ public sealed class SAV4HGSS : SAV4
return value;
}
public override int GetBoxWallpaper(int box)
public int GetBoxWallpaper(int box)
{
int offset = GetBoxWallpaperOffset(box);
int value = Storage[offset];
return AdjustWallpaper(value, -0x10);
}
public override void SetBoxWallpaper(int box, int value)
public void SetBoxWallpaper(int box, int value)
{
value = AdjustWallpaper(value, 0x10);
Storage[GetBoxWallpaperOffset(box)] = (byte)value;

View file

@ -12,16 +12,19 @@ public sealed class SAV4Pt : SAV4Sinnoh
public SAV4Pt() : base(GeneralSize, StorageSize)
{
Initialize();
Dex = new Zukan4(this, PokeDex);
Mystery = new MysteryBlock4Pt(this, GeneralBuffer.Slice(OffsetMystery, MysteryBlock4Pt.Size));
Dex = new Zukan4(this, GeneralBuffer[PokeDex..]);
}
public SAV4Pt(byte[] data) : base(data, GeneralSize, StorageSize, GeneralSize)
{
Initialize();
Dex = new Zukan4(this, PokeDex);
Mystery = new MysteryBlock4Pt(this, GeneralBuffer.Slice(OffsetMystery, MysteryBlock4Pt.Size));
Dex = new Zukan4(this, GeneralBuffer[PokeDex..]);
}
public override Zukan4 Dex { get; }
public override MysteryBlock4Pt Mystery { get; }
protected override SAV4 CloneInternal4() => State.Exportable ? new SAV4Pt((byte[])Data.Clone()) : new SAV4Pt();
public override GameVersion Version { get => GameVersion.Pt; set { } }
public override PersonalTable4 Personal => PersonalTable.Pt;
@ -43,14 +46,12 @@ public sealed class SAV4Pt : SAV4Sinnoh
new BlockInfo4(5, 0x2A000, 0x1D60), // Battle Video (Other Videos 3)
];
private void Initialize()
{
Version = GameVersion.Pt;
GetSAVOffsets();
}
private void Initialize() => GetSAVOffsets();
protected override int EventWork => 0xDAC;
protected override int EventFlag => 0xFEC;
private const int OffsetMystery = 0xB5C0;
protected override int DaycareOffset => 0x1654;
public override BattleFrontierFacility4 MaxFacility => BattleFrontierFacility4.Arcade;
private const int OFS_AccessoryMultiCount = 0x4E38; // 4 bits each
@ -59,19 +60,19 @@ public sealed class SAV4Pt : SAV4Sinnoh
private const int OFS_ToughWord = 0xCEB4;
private const int OFS_VillaFurniture = 0x111F;
private const int PokeDex = 0x1328;
public override bool HasPokeDex => true;
private void GetSAVOffsets()
{
AdventureInfo = 0;
Trainer1 = 0x68;
Party = 0xA0;
PokeDex = 0x1328;
Extra = 0x2820;
ChatterOffset = 0x64EC;
Geonet = 0xA4C4;
WondercardFlags = 0xB4C0;
WondercardData = 0xB5C0;
DaycareOffset = 0x1654;
OFS_HONEY = 0x7F38;
OFS_UG_Stats = 0x3CB4;

View file

@ -7,7 +7,7 @@ namespace PKHeX.Core;
/// <summary>
/// Abstract <see cref="SaveFile"/> format for <see cref="GameVersion.DP"/> and <see cref="GameVersion.Pt"/>
/// </summary>
public abstract class SAV4Sinnoh : SAV4
public abstract class SAV4Sinnoh : SAV4, IBoxDetailName, IBoxDetailWallpaper
{
protected override int FooterSize => 0x14;
protected SAV4Sinnoh([ConstantExpected] int gSize, [ConstantExpected] int sSize) : base(gSize, sSize) { }
@ -30,7 +30,7 @@ public abstract class SAV4Sinnoh : SAV4
public override int GetBoxOffset(int box) => 4 + (box * BOX_DATA_LEN);
private static int GetBoxNameOffset(int box) => BOX_NAME + (box * BOX_NAME_LEN);
protected override int GetBoxWallpaperOffset(int box) => BOX_WP + box;
protected static int GetBoxWallpaperOffset(int box) => BOX_WP + box;
public override int CurrentBox // (align 32)
{
@ -45,14 +45,16 @@ public abstract class SAV4Sinnoh : SAV4
}
private Span<byte> GetBoxNameSpan(int box) => Storage.Slice(GetBoxNameOffset(box), BOX_NAME_LEN);
public override string GetBoxName(int box) => GetString(GetBoxNameSpan(box));
public string GetBoxName(int box) => GetString(GetBoxNameSpan(box));
public override void SetBoxName(int box, ReadOnlySpan<char> value)
public void SetBoxName(int box, ReadOnlySpan<char> value)
{
const int maxlen = 8;
var span = GetBoxNameSpan(box);
SetString(span, value, maxlen, StringConverterOption.ClearZero);
}
public abstract int GetBoxWallpaper(int box);
public abstract void SetBoxWallpaper(int box, int value);
#endregion
#region Poketch

View file

@ -8,7 +8,7 @@ namespace PKHeX.Core;
/// <summary>
/// Generation 5 <see cref="SaveFile"/> object.
/// </summary>
public abstract class SAV5 : SaveFile, ISaveBlock5BW, IEventFlag37
public abstract class SAV5 : SaveFile, ISaveBlock5BW, IEventFlagProvider37, IBoxDetailName, IBoxDetailWallpaper, IDaycareRandomState<ulong>, IDaycareStorage, IDaycareExperience, IDaycareEggState, IMysteryGiftStorageProvider
{
protected override PK5 GetPKM(byte[] data) => new(data);
protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray45(data);
@ -28,11 +28,6 @@ public abstract class SAV5 : SaveFile, ISaveBlock5BW, IEventFlag37
public override EntityContext Context => EntityContext.Gen5;
public override int MaxStringLengthOT => 7;
public override int MaxStringLengthNickname => 10;
protected override int GiftCountMax => 12;
public abstract int EventFlagCount { get; }
public abstract int EventWorkCount { get; }
protected abstract int EventFlagOffset { get; }
protected abstract int EventWorkOffset { get; }
public override ushort MaxMoveID => Legal.MaxMoveID_5;
public override ushort MaxSpeciesID => Legal.MaxSpeciesID_5;
@ -55,7 +50,6 @@ public abstract class SAV5 : SaveFile, ISaveBlock5BW, IEventFlag37
{
Box = 0x400;
Party = 0x18E00;
AdventureInfo = 0x1D900;
}
// Blocks & Offsets
@ -65,19 +59,16 @@ public abstract class SAV5 : SaveFile, ISaveBlock5BW, IEventFlag37
protected int CGearInfoOffset;
protected int CGearDataOffset;
protected int EntreeForestOffset;
private int AdventureInfo;
public abstract int GTS { get; }
public int PGL => AllBlocks[35].Offset + 8; // Dream World Upload
// Daycare
public override int DaycareSeedSize => Daycare5.DaycareSeedSize;
public override bool? IsDaycareOccupied(int loc, int slot) => Daycare.IsOccupied(slot);
public override int GetDaycareSlotOffset(int loc, int slot) => Daycare.GetPKMOffset(slot);
public override uint? GetDaycareEXP(int loc, int slot) => Daycare.GetEXP(slot);
public override void SetDaycareEXP(int loc, int slot, uint EXP) => Daycare.SetEXP(slot, EXP);
public override void SetDaycareOccupied(int loc, int slot, bool occupied) => Daycare.SetOccupied(slot, occupied);
public override void SetDaycareRNGSeed(int loc, string seed) => Daycare.SetSeed(seed);
public int DaycareSlotCount => 2;
public Memory<byte> GetDaycareSlot(int slot) => Daycare.GetDaycareSlot(slot);
public bool IsDaycareOccupied(int slot) => Daycare.IsDaycareOccupied(slot);
public uint GetDaycareEXP(int slot) => Daycare.GetDaycareEXP(slot);
public void SetDaycareEXP(int slot, uint value) => Daycare.SetDaycareEXP(slot, value);
public void SetDaycareOccupied(int slot, bool occupied) => Daycare.SetDaycareOccupied(slot, occupied);
public bool IsEggAvailable { get => Daycare.IsEggAvailable; set => Daycare.IsEggAvailable = value; }
ulong IDaycareRandomState<ulong>.Seed { get => Daycare.Seed; set => Daycare.Seed = value; }
// Storage
public override int PartyCount
@ -89,22 +80,13 @@ public abstract class SAV5 : SaveFile, ISaveBlock5BW, IEventFlag37
public override int GetBoxOffset(int box) => Box + (SIZE_STORED * box * 30) + (box * 0x10);
public override int GetPartyOffset(int slot) => Party + 8 + (SIZE_PARTY * slot);
protected override int GetBoxWallpaperOffset(int box) => BoxLayout.GetBoxWallpaperOffset(box);
public override int BoxesUnlocked { get => BoxLayout.BoxesUnlocked; set => BoxLayout.BoxesUnlocked = (byte)value; }
public override int GetBoxWallpaper(int box) => BoxLayout.GetBoxWallpaper(box);
public override void SetBoxWallpaper(int box, int value) => BoxLayout.SetBoxWallpaper(box, value);
public override string GetBoxName(int box) => BoxLayout[box];
public override void SetBoxName(int box, ReadOnlySpan<char> value) => BoxLayout.SetBoxName(box, value);
public int GetBoxWallpaper(int box) => BoxLayout.GetBoxWallpaper(box);
public void SetBoxWallpaper(int box, int value) => BoxLayout.SetBoxWallpaper(box, value);
public string GetBoxName(int box) => BoxLayout[box];
public void SetBoxName(int box, ReadOnlySpan<char> value) => BoxLayout.SetBoxName(box, value);
public override int CurrentBox { get => BoxLayout.CurrentBox; set => BoxLayout.CurrentBox = value; }
protected int BattleBoxOffset;
public bool BattleBoxLocked
{
get => Data[BattleBoxOffset + 0x358] != 0; // Wi-Fi/Live Tournament Active
set => Data[BattleBoxOffset + 0x358] = value ? (byte)1 : (byte)0;
}
protected override void SetPKM(PKM pk, bool isParty = false)
{
var pk5 = (PK5)pk;
@ -127,9 +109,8 @@ public abstract class SAV5 : SaveFile, ISaveBlock5BW, IEventFlag37
public override int PlayedMinutes { get => PlayerData.PlayedMinutes; set => PlayerData.PlayedMinutes = value; }
public override int PlayedSeconds { get => PlayerData.PlayedSeconds; set => PlayerData.PlayedSeconds = value; }
public override uint Money { get => Misc.Money; set => Misc.Money = value; }
public override uint SecondsToStart { get => ReadUInt32LittleEndian(Data.AsSpan(AdventureInfo + 0x34)); set => WriteUInt32LittleEndian(Data.AsSpan(AdventureInfo + 0x34), value); }
public override uint SecondsToFame { get => ReadUInt32LittleEndian(Data.AsSpan(AdventureInfo + 0x3C)); set => WriteUInt32LittleEndian(Data.AsSpan(AdventureInfo + 0x3C), value); }
public override MysteryGiftAlbum GiftAlbum { get => Mystery.GiftAlbum; set => Mystery.GiftAlbum = (EncryptedMysteryGiftAlbum)value; }
public override uint SecondsToStart { get => AdventureInfo.SecondsToStart; set => AdventureInfo.SecondsToStart = value; }
public override uint SecondsToFame { get => AdventureInfo.SecondsToFame ; set => AdventureInfo.SecondsToFame = value; }
public override IReadOnlyList<InventoryPouch> Inventory { get => Items.Inventory; set => Items.Inventory = value; }
protected override void SetDex(PKM pk) => Zukan.SetDex(pk);
@ -143,30 +124,13 @@ public abstract class SAV5 : SaveFile, ISaveBlock5BW, IEventFlag37
return StringConverter5.SetString(destBuffer, value, maxLength, option);
}
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(EventFlagOffset + (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}).");
SetFlag(EventFlagOffset + (flagNumber >> 3), flagNumber & 7, value);
}
public ushort GetWork(int index) => ReadUInt16LittleEndian(Data.AsSpan(EventWorkOffset + (index * 2)));
public void SetWork(int index, ushort value) => WriteUInt16LittleEndian(Data.AsSpan(EventWorkOffset)[(index * 2)..], value);
// DLC
private int CGearSkinInfoOffset => CGearInfoOffset + (this is SAV5B2W2 ? 0x10 : 0) + 0x24;
private bool CGearSkinPresent
{
get => Data[CGearSkinInfoOffset + 2] == 1;
set => Data[CGearSkinInfoOffset + 2] = Data[PlayerData.Offset + (this is SAV5B2W2 ? 0x6C : 0x54)] = value ? (byte)1 : (byte)0;
set => Data[CGearSkinInfoOffset + 2] = PlayerData.Data[this is SAV5B2W2 ? 0x6C : 0x54] = value ? (byte)1 : (byte)0;
}
private static ReadOnlySpan<byte> DLCFooter => [ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x27, 0x00, 0x00, 0x27, 0x35, 0x05, 0x31, 0x00, 0x00 ];
@ -205,12 +169,6 @@ public abstract class SAV5 : SaveFile, ISaveBlock5BW, IEventFlag37
}
}
public EntreeForest EntreeData
{
get => new(Data.AsSpan(EntreeForestOffset, EntreeForest.SIZE).ToArray());
set => SetData(value.Write(), EntreeForestOffset);
}
public abstract IReadOnlyList<BlockInfo> AllBlocks { get; }
public abstract MyItem Items { get; }
public abstract Zukan5 Zukan { get; }
@ -225,10 +183,24 @@ public abstract class SAV5 : SaveFile, ISaveBlock5BW, IEventFlag37
public abstract Musical5 Musical { get; }
public abstract Encount5 Encount { get; }
public abstract UnityTower5 UnityTower { get; }
public abstract EventWork5 EventWork { get; }
public abstract BattleBox5 BattleBox { get; }
public abstract EntreeForest EntreeForest { get; }
public abstract GlobalLink5 GlobalLink { get; }
public abstract WhiteBlack5 Forest { get; }
public abstract GTS5 GTS { get; }
public abstract AdventureInfo5 AdventureInfo { get; }
IEventFlag37 IEventFlagProvider37.EventWork => EventWork;
protected override byte[] GetFinalData()
{
EntreeForest.EndAccess();
Mystery.EndAccess();
return base.GetFinalData();
}
public static int GetMailOffset(int index) => (index * Mail5.SIZE) + 0x1DD00;
public byte[] GetMailData(int offset) => Data.AsSpan(offset, Mail5.SIZE).ToArray();
public int GetBattleBoxSlot(int slot) => BattleBoxOffset + (slot * SIZE_STORED);
public MailDetail GetMail(int mailIndex)
{
@ -236,4 +208,6 @@ public abstract class SAV5 : SaveFile, ISaveBlock5BW, IEventFlag37
var data = GetMailData(ofs);
return new Mail5(data, ofs);
}
IMysteryGiftStorage IMysteryGiftStorageProvider.MysteryGiftStorage => Mystery;
}

View file

@ -24,23 +24,16 @@ public sealed class SAV5B2W2 : SAV5, ISaveBlock5B2W2
public override PersonalTable5B2W2 Personal => PersonalTable.B2W2;
public SaveBlockAccessor5B2W2 Blocks { get; }
protected override SAV5B2W2 CloneInternal() => new((byte[]) Data.Clone());
public override int EventWorkCount => 0x1AF; // this doesn't seem right?
public override int EventFlagCount => 0xBF8;
protected override int EventWorkOffset => 0x1FF00;
protected override int EventFlagOffset => EventWorkOffset + 0x35E;
public override int MaxItemID => Legal.MaxItemID_5_B2W2;
private void Initialize()
{
BattleBoxOffset = 0x20900;
CGearInfoOffset = 0x1C000;
CGearDataOffset = 0x52800;
EntreeForestOffset = 0x22A00;
PokeDex = Blocks.Zukan.PokeDex;
WondercardData = Blocks.Mystery.Offset;
DaycareOffset = Blocks.Daycare.Offset;
}
public override bool HasPokeDex => true;
public override IReadOnlyList<BlockInfo> AllBlocks => Blocks.BlockInfo;
public override MyItem Items => Blocks.Items;
public override Zukan5 Zukan => Blocks.Zukan;
@ -55,11 +48,17 @@ public sealed class SAV5B2W2 : SAV5, ISaveBlock5B2W2
public override Musical5 Musical => Blocks.Musical;
public override Encount5 Encount => Blocks.Encount;
public override UnityTower5 UnityTower => Blocks.UnityTower;
public override EventWork5B2W2 EventWork => Blocks.EventWork;
public override BattleBox5 BattleBox => Blocks.BattleBox;
public override EntreeForest EntreeForest => Blocks.EntreeForest;
public override GlobalLink5 GlobalLink => Blocks.GlobalLink;
public override GTS5 GTS => Blocks.GTS;
public override WhiteBlack5B2W2 Forest => Blocks.Forest;
public override AdventureInfo5 AdventureInfo => Blocks.AdventureInfo;
public FestaBlock5 Festa => Blocks.Festa;
public PWTBlock5 PWT => Blocks.PWT;
public MedalList5 Medals => Blocks.Medals;
public int Fused => 0x1FA00 + sizeof(uint);
public override int GTS => 0x20400;
public string Rival
{
@ -72,6 +71,4 @@ public sealed class SAV5B2W2 : SAV5, ISaveBlock5B2W2
get => Data.AsSpan(0x23BA4, MaxStringLengthOT * 2);
set { if (value.Length == MaxStringLengthOT * 2) value.CopyTo(Data.AsSpan(0x23BA4)); }
}
public override string GetDaycareRNGSeed(int loc) => $"{Daycare.GetSeed()!:X16}";
}

View file

@ -23,36 +23,35 @@ public sealed class SAV5BW : SAV5
public override PersonalTable5BW Personal => PersonalTable.BW;
public SaveBlockAccessor5BW Blocks { get; }
protected override SAV5BW CloneInternal() => new((byte[])Data.Clone());
public override int EventWorkCount => 0x13E;
public override int EventFlagCount => 0xB60;
protected override int EventWorkOffset => 0x20100;
protected override int EventFlagOffset => EventWorkOffset + 0x27C;
public override int MaxItemID => Legal.MaxItemID_5_BW;
public override bool HasPokeDex => true;
private void Initialize()
{
BattleBoxOffset = 0x20A00;
CGearInfoOffset = 0x1C000;
CGearDataOffset = 0x52000;
EntreeForestOffset = 0x22C00;
PokeDex = Blocks.Zukan.PokeDex;
WondercardData = Blocks.Mystery.Offset;
DaycareOffset = Blocks.Daycare.Offset;
}
public override IReadOnlyList<BlockInfo> AllBlocks => Blocks.BlockInfo;
public override MyItem Items => Blocks.Items;
public override MyItem5BW Items => Blocks.Items;
public override Zukan5 Zukan => Blocks.Zukan;
public override Misc5 Misc => Blocks.Misc;
public override Misc5BW Misc => Blocks.Misc;
public override MysteryBlock5 Mystery => Blocks.Mystery;
public override Chatter5 Chatter => Blocks.Chatter;
public override Daycare5 Daycare => Blocks.Daycare;
public override BoxLayout5 BoxLayout => Blocks.BoxLayout;
public override PlayerData5 PlayerData => Blocks.PlayerData;
public override BattleSubway5 BattleSubway => Blocks.BattleSubway;
public override Entralink5 Entralink => Blocks.Entralink;
public override Entralink5BW Entralink => Blocks.Entralink;
public override Musical5 Musical => Blocks.Musical;
public override Encount5 Encount => Blocks.Encount;
public override Encount5BW Encount => Blocks.Encount;
public override UnityTower5 UnityTower => Blocks.UnityTower;
public override int GTS => 0x20500;
public override EventWork5BW EventWork => Blocks.EventWork;
public override BattleBox5 BattleBox => Blocks.BattleBox;
public override EntreeForest EntreeForest => Blocks.EntreeForest;
public override GlobalLink5 GlobalLink => Blocks.GlobalLink;
public override GTS5 GTS => Blocks.GTS;
public override WhiteBlack5BW Forest => Blocks.Forest;
public override AdventureInfo5 AdventureInfo => Blocks.AdventureInfo;
}

View file

@ -1,14 +1,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
/// <summary>
/// Generation 6 <see cref="SaveFile"/> object.
/// </summary>
public abstract class SAV6 : SAV_BEEF, ITrainerStatRecord, ISaveBlock6Core, IRegionOrigin, IGameSync, IEventFlag37
public abstract class SAV6 : SAV_BEEF, ITrainerStatRecord, ISaveBlock6Core, IRegionOrigin, IGameSync, IEventFlagProvider37
{
// Save Data Attributes
protected internal override string ShortSummary => $"{OT} ({Version}) - {Played.LastSavedTime}";
@ -18,21 +17,15 @@ public abstract class SAV6 : SAV_BEEF, ITrainerStatRecord, ISaveBlock6Core, IReg
protected SAV6([ConstantExpected] int size, [ConstantExpected] int biOffset) : base(size, biOffset) { }
// Configuration
protected override int SIZE_STORED => PokeCrypto.SIZE_6STORED;
protected override int SIZE_PARTY => PokeCrypto.SIZE_6PARTY;
public override PK6 BlankPKM => new();
public override Type PKMType => typeof(PK6);
protected sealed override int SIZE_STORED => PokeCrypto.SIZE_6STORED;
protected sealed override int SIZE_PARTY => PokeCrypto.SIZE_6PARTY;
public sealed override PK6 BlankPKM => new();
public sealed override Type PKMType => typeof(PK6);
public override int BoxCount => 31;
public override int MaxEV => EffortValues.Max252;
public override byte Generation => 6;
public override EntityContext Context => EntityContext.Gen6;
protected override int GiftCountMax => 24;
protected override int GiftFlagMax => 0x100 * 8;
public int EventFlagCount => 8 * 0x1A0;
public int EventWorkCount => (EventFlag - EventWork) / sizeof(ushort);
protected abstract int EventFlag { get; }
protected abstract int EventWork { get; }
public sealed override int BoxCount => 31;
public sealed override int MaxEV => EffortValues.Max252;
public sealed override byte Generation => 6;
public sealed override EntityContext Context => EntityContext.Gen6;
public override int MaxStringLengthOT => 12;
public override int MaxStringLengthNickname => 12;
@ -43,14 +36,9 @@ public abstract class SAV6 : SAV_BEEF, ITrainerStatRecord, ISaveBlock6Core, IReg
protected override PK6 GetPKM(byte[] data) => new(data);
protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray6(data);
protected int WondercardFlags { get; set; } = int.MinValue;
protected int JPEG { get; set; } = int.MinValue;
public int PSS { get; protected set; } = int.MinValue;
public int BerryField { get; protected set; } = int.MinValue;
public int HoF { get; protected set; } = int.MinValue;
protected int PCLayout { private get; set; } = int.MinValue;
protected int BattleBoxOffset { get; set; } = int.MinValue;
public int GetBattleBoxSlot(int slot) => BattleBoxOffset + (slot * SIZE_STORED);
public virtual string JPEGTitle => string.Empty;
public virtual byte[] GetJPEGData() => [];
@ -84,29 +72,11 @@ public abstract class SAV6 : SAV_BEEF, ITrainerStatRecord, ISaveBlock6Core, IReg
public override uint SecondsToFame { get => GameTime.SecondsToFame; set => GameTime.SecondsToFame = value; }
public override IReadOnlyList<InventoryPouch> Inventory { get => Items.Inventory; set => Items.Inventory = value; }
// Daycare
public override int DaycareSeedSize => 16;
// Storage
public override int GetPartyOffset(int slot) => Party + (SIZE_PARTY * slot);
public override int GetBoxOffset(int box) => Box + (SIZE_STORED * box * 30);
private int GetBoxNameOffset(int box) => PCLayout + (LongStringLength * box);
public override string GetBoxName(int box)
{
if (PCLayout < 0)
return $"B{box + 1}";
return GetString(Data.AsSpan(GetBoxNameOffset(box), LongStringLength));
}
public override void SetBoxName(int box, ReadOnlySpan<char> value)
{
var span = Data.AsSpan(GetBoxNameOffset(box), LongStringLength);
SetString(span, value, LongStringLength / 2, StringConverterOption.ClearZero);
}
protected override void SetPKM(PKM pk, bool isParty = false)
{
PK6 pk6 = (PK6)pk;
@ -187,21 +157,6 @@ public abstract class SAV6 : SAV_BEEF, ITrainerStatRecord, ISaveBlock6Core, IReg
public abstract PlayTime6 Played { get; }
public abstract MyStatus6 Status { get; }
public abstract RecordBlock6 Records { get; }
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 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(Data.AsSpan(EventWork + (index * 2)));
public void SetWork(int index, ushort value) => WriteUInt16LittleEndian(Data.AsSpan(EventWork)[(index * 2)..], value);
public abstract EventWork6 EventWork { get; }
IEventFlag37 IEventFlagProvider37.EventWork => EventWork;
}

View file

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
@ -8,7 +7,7 @@ namespace PKHeX.Core;
/// Generation 6 <see cref="SaveFile"/> object for <see cref="GameVersion.ORAS"/>.
/// </summary>
/// <inheritdoc cref="SAV6" />
public sealed class SAV6AO : SAV6, ISaveBlock6AO, IMultiplayerSprite
public sealed class SAV6AO : SAV6, ISaveBlock6AO, IMultiplayerSprite, IBoxDetailName, IBoxDetailWallpaper, IDaycareMulti
{
public SAV6AO(byte[] data) : base(data, SaveBlockAccessor6AO.BlockMetadataOffset)
{
@ -31,44 +30,32 @@ public sealed class SAV6AO : SAV6, ISaveBlock6AO, IMultiplayerSprite
public override int MaxItemID => Legal.MaxItemID_6_AO;
public override int MaxAbilityID => Legal.MaxAbilityID_6_AO;
protected override int EventWork => 0x14A00;
protected override int EventFlag => EventWork + 0x2F0;
public override bool HasPokeDex => true;
private void Initialize()
{
PCLayout = 0x04400;
BattleBoxOffset = 0x04A00;
PSS = 0x05000;
Party = 0x14200;
PokeDex = 0x15000;
HoF = 0x19E00;
DaycareOffset = 0x1BC00;
BerryField = 0x1C400;
WondercardFlags = 0x1CC00;
Box = 0x33000;
JPEG = 0x67C00;
WondercardData = WondercardFlags + 0x100;
}
/// <summary> Offset of the UnionPokemon block. </summary>
public const int Fused = 0x16A00;
/// <summary> Offset of the GtsData block. </summary>
public const int GTS = 0x18200;
/// <summary> Offset of the second daycare structure within the Daycare block. </summary>
private const int Daycare2 = 0x1BC00 + 0x1F0;
/// <summary> Offset of the Contest data block. </summary>
public const int Contest = 0x23600;
#region Blocks
public override IReadOnlyList<BlockInfo> AllBlocks => Blocks.BlockInfo;
public override MyItem Items => Blocks.Items;
public override MyItem6AO Items => Blocks.Items;
public override ItemInfo6 ItemInfo => Blocks.ItemInfo;
public override GameTime6 GameTime => Blocks.GameTime;
public override Situation6 Situation => Blocks.Situation;
public override PlayTime6 Played => Blocks.Played;
public override MyStatus6 Status => Blocks.Status;
public override RecordBlock6 Records => Blocks.Records;
public override RecordBlock6AO Records => Blocks.Records;
public override EventWork6 EventWork => Blocks.EventWork;
public UnionPokemon6 Fused => Blocks.Fused;
public GTS6 GTS => Blocks.GTS;
public Puff6 Puff => Blocks.Puff;
public OPower6 OPower => Blocks.OPower;
public LinkBlock6 Link => Blocks.Link;
@ -77,13 +64,17 @@ public sealed class SAV6AO : SAV6, ISaveBlock6AO, IMultiplayerSprite
public MysteryBlock6 MysteryGift => Blocks.MysteryGift;
public SuperTrainBlock SuperTrain => Blocks.SuperTrain;
public MaisonBlock Maison => Blocks.Maison;
public SubEventLog6 SUBE => Blocks.SUBE;
public SubEventLog6AO SUBE => Blocks.SUBE;
public ConfigSave6 Config => Blocks.Config;
public Encount6 Encount => Blocks.Encount;
public Misc6AO Misc => Blocks.Misc;
public Zukan6AO Zukan => Blocks.Zukan;
public SecretBase6Block SecretBase => Blocks.SecretBase;
public BerryField6AO BerryField => Blocks.BerryField;
MyItem ISaveBlock6Core.Items => Items;
SubEventLog6 ISaveBlock6Main.SUBE => SUBE;
RecordBlock6 ISaveBlock6Core.Records => Records;
#endregion
public override bool IsVersionValid() => Version is GameVersion.AS or GameVersion.OR;
@ -106,84 +97,25 @@ public sealed class SAV6AO : SAV6, ISaveBlock6AO, IMultiplayerSprite
}
// Daycare
public override int DaycareSeedSize => 16;
public override bool HasTwoDaycares => true;
public override int GetDaycareSlotOffset(int loc, int slot)
{
int ofs = loc == 0 ? DaycareOffset : Daycare2;
return ofs + 8 + (slot * (SIZE_STORED + 8));
}
public override uint? GetDaycareEXP(int loc, int slot)
{
int ofs = loc == 0 ? DaycareOffset : Daycare2;
return ReadUInt32LittleEndian(Data.AsSpan(ofs + ((SIZE_STORED + 8) * slot) + 4));
}
public override bool? IsDaycareOccupied(int loc, int slot)
{
int ofs = loc == 0 ? DaycareOffset : Daycare2;
return Data[ofs + ((SIZE_STORED + 8) * slot)] == 1;
}
public override string GetDaycareRNGSeed(int loc)
{
int ofs = loc == 0 ? DaycareOffset : Daycare2;
return Util.GetHexStringFromBytes(Data.AsSpan(ofs + 0x1E8, DaycareSeedSize / 2));
}
public override bool? IsDaycareHasEgg(int loc)
{
int ofs = loc == 0 ? DaycareOffset : Daycare2;
return Data[ofs + 0x1E0] == 1;
}
public override void SetDaycareEXP(int loc, int slot, uint EXP)
{
int ofs = loc == 0 ? DaycareOffset : Daycare2;
WriteUInt32LittleEndian(Data.AsSpan(ofs + ((SIZE_STORED + 8) * slot) + 4), EXP);
}
public override void SetDaycareOccupied(int loc, int slot, bool occupied)
{
int ofs = loc == 0 ? DaycareOffset : Daycare2;
Data[ofs + ((SIZE_STORED + 8) * slot)] = occupied ? (byte)1 : (byte)0;
}
public override void SetDaycareRNGSeed(int loc, string seed)
{
if (loc != 0)
return;
if (DaycareOffset < 0)
return;
if (seed.Length > DaycareSeedSize)
return;
Util.GetBytesFromHexString(seed).CopyTo(Data, DaycareOffset + 0x1E8);
}
public override void SetDaycareHasEgg(int loc, bool hasEgg)
{
int ofs = loc == 0 ? DaycareOffset : Daycare2;
Data[ofs + 0x1E0] = hasEgg ? (byte)1 : (byte)0;
}
public override string JPEGTitle => !HasJPPEGData ? string.Empty : StringConverter6.GetString(Data.AsSpan(JPEG, 0x1A));
public override byte[] GetJPEGData() => !HasJPPEGData ? [] : Data.AsSpan(JPEG + 0x54, 0xE004).ToArray();
private bool HasJPPEGData => Data[JPEG + 0x54] == 0xFF;
protected override bool[] MysteryGiftReceivedFlags { get => Blocks.MysteryGift.GetReceivedFlags(); set => Blocks.MysteryGift.SetReceivedFlags(value); }
protected override DataMysteryGift[] MysteryGiftCards { get => Blocks.MysteryGift.GetGifts(); set => Blocks.MysteryGift.SetGifts(value); }
public override string JPEGTitle => !HasJPEGData ? string.Empty : StringConverter6.GetString(Data.AsSpan(JPEG, 0x1A));
public override byte[] GetJPEGData() => !HasJPEGData ? [] : Data.AsSpan(JPEG + 0x54, 0xE004).ToArray();
private bool HasJPEGData => Data[JPEG + 0x54] == 0xFF;
public override int CurrentBox { get => Blocks.BoxLayout.CurrentBox; set => Blocks.BoxLayout.CurrentBox = value; }
protected override int GetBoxWallpaperOffset(int box) => Blocks.BoxLayout.GetBoxWallpaperOffset(box);
public override int BoxesUnlocked { get => Blocks.BoxLayout.BoxesUnlocked; set => Blocks.BoxLayout.BoxesUnlocked = value; }
public override byte[] BoxFlags { get => Blocks.BoxLayout.BoxFlags; set => Blocks.BoxLayout.BoxFlags = value; }
public int GetBoxWallpaper(int box) => BoxLayout.GetBoxWallpaper(box);
public void SetBoxWallpaper(int box, int wallpaper) => BoxLayout.SetBoxWallpaper(box, wallpaper);
public string GetBoxName(int box) => BoxLayout.GetBoxName(box);
public void SetBoxName(int box, ReadOnlySpan<char> name) => BoxLayout.SetBoxName(box, name);
public bool BattleBoxLocked
{
get => Blocks.BattleBox.Locked;
set => Blocks.BattleBox.Locked = value;
}
public int DaycareCount => 2;
public IDaycareStorage this[int index] => Blocks.Daycare[index];
}

View file

@ -7,7 +7,7 @@ namespace PKHeX.Core;
/// Generation 6 <see cref="SaveFile"/> object for <see cref="GameVersion.ORASDEMO"/>.
/// </summary>
/// <inheritdoc cref="SAV6" />
public sealed class SAV6AODemo : SAV6
public sealed class SAV6AODemo : SAV6, ISaveBlock6Core
{
public SAV6AODemo(byte[] data) : base(data, SaveBlockAccessor6AODemo.BlockMetadataOffset)
{
@ -27,8 +27,6 @@ public sealed class SAV6AODemo : SAV6
public override ushort MaxMoveID => Legal.MaxMoveID_6_AO;
public override int MaxItemID => Legal.MaxItemID_6_AO;
public override int MaxAbilityID => Legal.MaxAbilityID_6_AO;
protected override int EventWork => 0x04600;
protected override int EventFlag => EventWork + 0x2F0;
public SaveBlockAccessor6AODemo Blocks { get; }
private void Initialize()
@ -42,12 +40,16 @@ public sealed class SAV6AODemo : SAV6
public override int Vivillon { get => Blocks.Misc.Vivillon; set => Blocks.Misc.Vivillon = value; } // unused
public override int Badges { get => Blocks.Misc.Badges; set => Blocks.Misc.Badges = value; } // unused
public override int BP { get => Blocks.Misc.BP; set => Blocks.Misc.BP = value; } // unused
public override MyItem Items => Blocks.Items;
public override MyItem6AO Items => Blocks.Items;
public override ItemInfo6 ItemInfo => Blocks.ItemInfo;
public override GameTime6 GameTime => Blocks.GameTime;
public override Situation6 Situation => Blocks.Situation;
public override PlayTime6 Played => Blocks.Played;
public override MyStatus6 Status => Blocks.Status;
public override RecordBlock6 Records => Blocks.Records;
public override EventWork6 EventWork => Blocks.EventWork;
public override IReadOnlyList<BlockInfo> AllBlocks => Blocks.BlockInfo;
MyItem ISaveBlock6Core.Items => Items;
RecordBlock6 ISaveBlock6Core.Records => Records;
}

View file

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
@ -8,7 +7,7 @@ namespace PKHeX.Core;
/// Generation 6 <see cref="SaveFile"/> object for <see cref="GameVersion.XY"/>.
/// </summary>
/// <inheritdoc cref="SAV6" />
public sealed class SAV6XY : SAV6, ISaveBlock6XY, IMultiplayerSprite
public sealed class SAV6XY : SAV6, ISaveBlock6XY, IMultiplayerSprite, IBoxDetailName, IBoxDetailWallpaper, IDaycareStorage, IDaycareEggState, IDaycareExperience, IDaycareRandomState<ulong>
{
public SAV6XY(byte[] data) : base(data, SaveBlockAccessor6XY.BlockMetadataOffset)
{
@ -31,33 +30,20 @@ public sealed class SAV6XY : SAV6, ISaveBlock6XY, IMultiplayerSprite
public override int MaxItemID => Legal.MaxItemID_6_XY;
public override int MaxAbilityID => Legal.MaxAbilityID_6_XY;
protected override int EventWork => 0x14A00;
protected override int EventFlag => EventWork + 0x2F0;
public override bool HasPokeDex => true;
private void Initialize()
{
// Enable Features
Party = 0x14200;
PCLayout = 0x4400;
BattleBoxOffset = 0x04A00;
PSS = 0x05000;
PokeDex = 0x15000;
HoF = 0x19400;
DaycareOffset = 0x1B200;
BerryField = 0x1B800;
WondercardFlags = 0x1BC00;
Box = 0x22600;
JPEG = 0x57200;
WondercardData = WondercardFlags + 0x100;
// Extra Viewable Slots
Fused = 0x16000;
GTS = 0x17800;
}
public int GTS { get; private set; } = int.MinValue;
public int Fused { get; private set; } = int.MinValue;
public const int BerryField = 0x1B800;
#region Blocks
public override IReadOnlyList<BlockInfo> AllBlocks => Blocks.BlockInfo;
@ -68,6 +54,9 @@ public sealed class SAV6XY : SAV6, ISaveBlock6XY, IMultiplayerSprite
public override PlayTime6 Played => Blocks.Played;
public override MyStatus6 Status => Blocks.Status;
public override RecordBlock6 Records => Blocks.Records;
public override EventWork6 EventWork => Blocks.EventWork;
public UnionPokemon6 Fused => Blocks.Fused;
public GTS6 GTS => Blocks.GTS;
public Puff6 Puff => Blocks.Puff;
public OPower6 OPower => Blocks.OPower;
public LinkBlock6 Link => Blocks.Link;
@ -86,29 +75,24 @@ public sealed class SAV6XY : SAV6, ISaveBlock6XY, IMultiplayerSprite
protected override void SetDex(PKM pk) => Blocks.Zukan.SetDex(pk);
// Daycare
public override int DaycareSeedSize => 16;
public override bool HasTwoDaycares => false;
public override bool? IsDaycareOccupied(int loc, int slot) => Data[DaycareOffset + 0 + ((SIZE_STORED + 8) * slot)] == 1;
public override uint? GetDaycareEXP(int loc, int slot) => ReadUInt32LittleEndian(Data.AsSpan(DaycareOffset + 4 + ((SIZE_STORED + 8) * slot)));
// Daycare - delegate from block
public int DaycareSlotCount => Blocks.Daycare.DaycareSlotCount;
public Memory<byte> GetDaycareSlot(int index) => Blocks.Daycare.GetDaycareSlot(index);
public bool IsDaycareOccupied(int index) => Blocks.Daycare.IsDaycareOccupied(index);
public void SetDaycareOccupied(int index, bool occupied) => Blocks.Daycare.SetDaycareOccupied(index, occupied);
public uint GetDaycareEXP(int index) => Blocks.Daycare.GetDaycareEXP(index);
public void SetDaycareEXP(int index, uint exp) => Blocks.Daycare.SetDaycareEXP(index, exp);
public override int GetDaycareSlotOffset(int loc, int slot) => DaycareOffset + 8 + (slot * (SIZE_STORED + 8));
public override bool? IsDaycareHasEgg(int loc) => Data[DaycareOffset + 0x1E0] == 1;
public override void SetDaycareHasEgg(int loc, bool hasEgg) => Data[DaycareOffset + 0x1E0] = hasEgg ? (byte)1 : (byte)0;
public override void SetDaycareOccupied(int loc, int slot, bool occupied) => Data[DaycareOffset + ((SIZE_STORED + 8) * slot)] = occupied ? (byte)1 : (byte)0;
public override void SetDaycareEXP(int loc, int slot, uint EXP) => WriteUInt32LittleEndian(Data.AsSpan(DaycareOffset + 4 + ((SIZE_STORED + 8) * slot)), EXP);
public override string GetDaycareRNGSeed(int loc) => Util.GetHexStringFromBytes(Data.AsSpan(DaycareOffset + 0x1E8, DaycareSeedSize / 2));
public override void SetDaycareRNGSeed(int loc, string seed)
public bool IsEggAvailable
{
if (loc != 0)
return;
if (DaycareOffset < 0)
return;
if (seed.Length > DaycareSeedSize)
return;
get => Blocks.Daycare.IsEggAvailable;
set => Blocks.Daycare.IsEggAvailable = value;
}
Util.GetBytesFromHexString(seed).CopyTo(Data, DaycareOffset + 0x1E8);
ulong IDaycareRandomState<ulong>.Seed
{
get => Blocks.Daycare.Seed;
set => Blocks.Daycare.Seed = value;
}
public override string JPEGTitle => !HasJPPEGData ? string.Empty : StringConverter6.GetString(Data.AsSpan(JPEG, 0x1A));
@ -131,19 +115,18 @@ public sealed class SAV6XY : SAV6, ISaveBlock6XY, IMultiplayerSprite
}
public override bool IsVersionValid() => Version is GameVersion.X or GameVersion.Y;
protected override bool[] MysteryGiftReceivedFlags { get => Blocks.MysteryGift.GetReceivedFlags(); set => Blocks.MysteryGift.SetReceivedFlags(value); }
protected override DataMysteryGift[] MysteryGiftCards { get => Blocks.MysteryGift.GetGifts(); set => Blocks.MysteryGift.SetGifts(value); }
public override bool GetCaught(ushort species) => Blocks.Zukan.GetCaught(species);
public override bool GetSeen(ushort species) => Blocks.Zukan.GetSeen(species);
public override void SetSeen(ushort species, bool seen) => Blocks.Zukan.SetSeen(species, seen);
public override void SetCaught(ushort species, bool caught) => Blocks.Zukan.SetCaught(species, caught);
public override int CurrentBox { get => Blocks.BoxLayout.CurrentBox; set => Blocks.BoxLayout.CurrentBox = value; }
protected override int GetBoxWallpaperOffset(int box) => Blocks.BoxLayout.GetBoxWallpaperOffset(box);
public override int BoxesUnlocked { get => Blocks.BoxLayout.BoxesUnlocked; set => Blocks.BoxLayout.BoxesUnlocked = value; }
public override byte[] BoxFlags { get => Blocks.BoxLayout.BoxFlags; set => Blocks.BoxLayout.BoxFlags = value; }
public int GetBoxWallpaper(int box) => BoxLayout.GetBoxWallpaper(box);
public void SetBoxWallpaper(int box, int wallpaper) => BoxLayout.SetBoxWallpaper(box, wallpaper);
public string GetBoxName(int box) => BoxLayout.GetBoxName(box);
public void SetBoxName(int box, ReadOnlySpan<char> name) => BoxLayout.SetBoxName(box, name);
public bool BattleBoxLocked
{

View file

@ -1,14 +1,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
/// <summary>
/// Generation 7 <see cref="SaveFile"/> object.
/// </summary>
public abstract class SAV7 : SAV_BEEF, ITrainerStatRecord, ISaveBlock7Main, IRegionOrigin, IGameSync, IEventFlag37
public abstract class SAV7 : SAV_BEEF, ITrainerStatRecord, ISaveBlock7Main, IRegionOrigin, IGameSync, IEventFlagProvider37, IBoxDetailName, IBoxDetailWallpaper, IDaycareStorage, IDaycareEggState, IDaycareRandomState<UInt128>, IMysteryGiftStorageProvider
{
// Save Data Attributes
protected internal override string ShortSummary => $"{OT} ({Version}) - {Played.LastSavedTime}";
@ -30,7 +29,7 @@ public abstract class SAV7 : SAV_BEEF, ITrainerStatRecord, ISaveBlock7Main, IReg
protected void ReloadBattleTeams()
{
var demo = this is SAV7SM && !Data.AsSpan(BoxLayout.Offset, 0x4C4).ContainsAnyExcept<byte>(0); // up to Battle Box values
var demo = this is SAV7SM && !BoxLayout.Data[..0x4C4].ContainsAnyExcept<byte>(0); // up to Battle Box values
if (demo || !State.Exportable)
{
BoxLayout.ClearBattleTeams();
@ -61,7 +60,9 @@ public abstract class SAV7 : SAV_BEEF, ITrainerStatRecord, ISaveBlock7Main, IReg
public abstract ResortSave7 ResortSave { get; }
public abstract FieldMenu7 FieldMenu { get; }
public abstract FashionBlock7 Fashion { get; }
public abstract HallOfFame7 Fame { get; }
public abstract EventWork7 EventWork { get; }
public abstract UnionPokemon7 Fused { get; }
public abstract GTS7 GTS { get; }
#endregion
// Configuration
@ -74,12 +75,6 @@ public abstract class SAV7 : SAV_BEEF, ITrainerStatRecord, ISaveBlock7Main, IReg
public override int MaxEV => EffortValues.Max252;
public override byte Generation => 7;
public override EntityContext Context => EntityContext.Gen7;
protected override int GiftCountMax => 48;
protected override int GiftFlagMax => 0x100 * 8;
public abstract int EventFlagCount { get; }
public int EventWorkCount => 1000;
private int EventWork => AllBlocks[05].Offset;
private int EventFlag => EventWork + (EventWorkCount * 2); // After Event Const (u16)*n
public override int MaxStringLengthOT => 12;
public override int MaxStringLengthNickname => 12;
@ -156,11 +151,10 @@ public abstract class SAV7 : SAV_BEEF, ITrainerStatRecord, ISaveBlock7Main, IReg
// Storage
public override int GetPartyOffset(int slot) => Party + (SIZE_PARTY * slot);
public override int GetBoxOffset(int box) => Box + (SIZE_STORED * box * 30);
protected override int GetBoxWallpaperOffset(int box) => BoxLayout.GetBoxWallpaperOffset(box);
public override int GetBoxWallpaper(int box) => BoxLayout.GetBoxWallpaper(box);
public override void SetBoxWallpaper(int box, int value) => BoxLayout.SetBoxWallpaper(box, value);
public override string GetBoxName(int box) => BoxLayout[box];
public override void SetBoxName(int box, ReadOnlySpan<char> value) => BoxLayout.SetBoxName(box, value);
public int GetBoxWallpaper(int box) => BoxLayout.GetBoxWallpaper(box);
public void SetBoxWallpaper(int box, int value) => BoxLayout.SetBoxWallpaper(box, value);
public string GetBoxName(int box) => BoxLayout[box];
public void SetBoxName(int box, ReadOnlySpan<char> value) => BoxLayout.SetBoxName(box, value);
public override int CurrentBox { get => BoxLayout.CurrentBox; set => BoxLayout.CurrentBox = value; }
public override int BoxesUnlocked { get => BoxLayout.BoxesUnlocked; set => BoxLayout.BoxesUnlocked = value; }
public override byte[] BoxFlags { get => BoxLayout.BoxFlags; set => BoxLayout.BoxFlags = value; }
@ -237,31 +231,24 @@ public abstract class SAV7 : SAV_BEEF, ITrainerStatRecord, ISaveBlock7Main, IReg
return AllBlocks[08].Offset + (PokeCrypto.SIZE_6PARTY * slot); // 0x104*slot
}
public override int DaycareSeedSize => Daycare7.DaycareSeedSize; // 128 bits
public override int GetDaycareSlotOffset(int loc, int slot) => Daycare.GetDaycareSlotOffset(slot);
public override bool? IsDaycareOccupied(int loc, int slot) => Daycare.GetIsOccupied(slot);
public override string GetDaycareRNGSeed(int loc) => Daycare.RNGSeed;
public override bool? IsDaycareHasEgg(int loc) => Daycare.HasEgg;
public override void SetDaycareOccupied(int loc, int slot, bool occupied) => Daycare.SetOccupied(slot, occupied);
public override void SetDaycareRNGSeed(int loc, string seed) => Daycare.RNGSeed = seed;
public override void SetDaycareHasEgg(int loc, bool hasEgg) => Daycare.HasEgg = hasEgg;
// Daycare - delegate from block
public int DaycareSlotCount => Daycare.DaycareSlotCount;
public Memory<byte> GetDaycareSlot(int index) => Daycare.GetDaycareSlot(index);
public bool IsDaycareOccupied(int index) => Daycare.IsDaycareOccupied(index);
public void SetDaycareOccupied(int index, bool occupied) => Daycare.SetDaycareOccupied(index, occupied);
protected override bool[] MysteryGiftReceivedFlags { get => MysteryGift.MysteryGiftReceivedFlags; set => MysteryGift.MysteryGiftReceivedFlags = value; }
protected override DataMysteryGift[] MysteryGiftCards { get => MysteryGift.MysteryGiftCards; set => MysteryGift.MysteryGiftCards = value; }
public bool GetEventFlag(int flagNumber)
public bool IsEggAvailable
{
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);
get => Daycare.IsEggAvailable;
set => Daycare.IsEggAvailable = value;
}
public void SetEventFlag(int flagNumber, bool value)
UInt128 IDaycareRandomState<UInt128>.Seed
{
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);
get => Daycare.Seed;
set => Daycare.Seed = value;
}
public ushort GetWork(int index) => ReadUInt16LittleEndian(Data.AsSpan(EventWork + (index * 2)));
public void SetWork(int index, ushort value) => WriteUInt16LittleEndian(Data.AsSpan(EventWork)[(index * 2)..], value);
IEventFlag37 IEventFlagProvider37.EventWork => EventWork;
IMysteryGiftStorage IMysteryGiftStorageProvider.MysteryGiftStorage => MysteryGift;
}

View file

@ -20,15 +20,13 @@ public sealed class SAV7SM : SAV7, ISaveBlock7SM
ClearBoxes();
}
public override bool HasPokeDex => true;
private void Initialize()
{
Party = Blocks.BlockInfo[04].Offset;
PokeDex = Blocks.BlockInfo[06].Offset;
TeamSlots = Blocks.BoxLayout.TeamSlots;
Box = Blocks.BlockInfo[14].Offset;
WondercardData = Blocks.MysteryGift.Offset;
DaycareOffset = Blocks.Daycare.Offset;
ReloadBattleTeams();
}
@ -40,12 +38,12 @@ public sealed class SAV7SM : SAV7, ISaveBlock7SM
#region Blocks
public SaveBlockAccessor7SM Blocks { get; }
public override IReadOnlyList<BlockInfo> AllBlocks => Blocks.BlockInfo;
public override MyItem Items => Blocks.Items;
public override MyItem7SM Items => Blocks.Items;
public override MysteryBlock7 MysteryGift => Blocks.MysteryGift;
public override PokeFinder7 PokeFinder => Blocks.PokeFinder;
public override JoinFesta7 Festa => Blocks.Festa;
public override Daycare7 Daycare => Blocks.Daycare;
public override RecordBlock6 Records => Blocks.Records;
public override RecordBlock7SM Records => Blocks.Records;
public override PlayTime6 Played => Blocks.Played;
public override MyStatus7 MyStatus => Blocks.MyStatus;
public override FieldMoveModelSave7 Overworld => Blocks.Overworld;
@ -59,10 +57,11 @@ public sealed class SAV7SM : SAV7, ISaveBlock7SM
public override ResortSave7 ResortSave => Blocks.ResortSave;
public override FieldMenu7 FieldMenu => Blocks.FieldMenu;
public override FashionBlock7 Fashion => Blocks.Fashion;
public override HallOfFame7 Fame => Blocks.Fame;
public override EventWork7SM EventWork => Blocks.EventWork;
public override UnionPokemon7 Fused => Blocks.Fused;
public override GTS7 GTS => Blocks.GTS;
#endregion
public override int EventFlagCount => 4000;
public override ushort MaxMoveID => Legal.MaxMoveID_7;
public override ushort MaxSpeciesID => Legal.MaxSpeciesID_7;
public override int MaxItemID => Legal.MaxItemID_7;
@ -72,7 +71,7 @@ public sealed class SAV7SM : SAV7, ISaveBlock7SM
public void UpdateMagearnaConstant()
{
var flag = GetEventFlag(3100);
var flag = EventWork.GetEventFlag(EventWork7SM.MagearnaEventFlag); // 3100
ulong value = flag ? MagearnaConst : 0ul;
WriteUInt64LittleEndian(Data.AsSpan(Blocks.BlockInfo[35].Offset + 0x168), value);
}

View file

@ -18,15 +18,13 @@ public sealed class SAV7USUM : SAV7, ISaveBlock7USUM
Initialize();
}
public override bool HasPokeDex => true;
private void Initialize()
{
Party = Blocks.BlockInfo[04].Offset;
PokeDex = Blocks.BlockInfo[06].Offset;
TeamSlots = Blocks.BoxLayout.TeamSlots;
Box = Blocks.BlockInfo[14].Offset;
WondercardData = Blocks.MysteryGift.Offset;
DaycareOffset = Blocks.Daycare.Offset;
ReloadBattleTeams();
}
@ -34,7 +32,6 @@ public sealed class SAV7USUM : SAV7, ISaveBlock7USUM
public override PersonalTable7 Personal => PersonalTable.USUM;
public override ReadOnlySpan<ushort> HeldItems => Legal.HeldItems_USUM;
protected override SAV7USUM CloneInternal() => new((byte[])Data.Clone());
public override int EventFlagCount => 4960;
public override ushort MaxMoveID => Legal.MaxMoveID_7_USUM;
public override ushort MaxSpeciesID => Legal.MaxSpeciesID_7_USUM;
public override int MaxItemID => Legal.MaxItemID_7_USUM;
@ -45,12 +42,12 @@ public sealed class SAV7USUM : SAV7, ISaveBlock7USUM
#region Blocks
public SaveBlockAccessor7USUM Blocks { get; }
public override IReadOnlyList<BlockInfo> AllBlocks => Blocks.BlockInfo;
public override MyItem Items => Blocks.Items;
public override MyItem7USUM Items => Blocks.Items;
public override MysteryBlock7 MysteryGift => Blocks.MysteryGift;
public override PokeFinder7 PokeFinder => Blocks.PokeFinder;
public override JoinFesta7 Festa => Blocks.Festa;
public override Daycare7 Daycare => Blocks.Daycare;
public override RecordBlock6 Records => Blocks.Records;
public override RecordBlock7USUM Records => Blocks.Records;
public override PlayTime6 Played => Blocks.Played;
public override MyStatus7 MyStatus => Blocks.MyStatus;
public override FieldMoveModelSave7 Overworld => Blocks.Overworld;
@ -64,7 +61,9 @@ public sealed class SAV7USUM : SAV7, ISaveBlock7USUM
public override ResortSave7 ResortSave => Blocks.ResortSave;
public override FieldMenu7 FieldMenu => Blocks.FieldMenu;
public override FashionBlock7 Fashion => Blocks.Fashion;
public override HallOfFame7 Fame => Blocks.Fame;
public override EventWork7USUM EventWork => Blocks.EventWork;
public override UnionPokemon7 Fused => Blocks.Fused;
public override GTS7 GTS => Blocks.GTS;
public BattleAgency7 BattleAgency => Blocks.BattleAgency;
#endregion
}

View file

@ -6,7 +6,7 @@ namespace PKHeX.Core;
/// <summary>
/// Generation 7 <see cref="SaveFile"/> object for <see cref="GameVersion.GG"/> games.
/// </summary>
public sealed class SAV7b : SAV_BEEF, ISaveBlock7b, IGameSync, IEventFlagArray
public sealed class SAV7b : SAV_BEEF, ISaveBlock7b, IGameSync, IMysteryGiftStorageProvider
{
protected internal override string ShortSummary => $"{OT} ({Version}) - {Blocks.Played.LastSavedTime}";
public override string Extension => ".bin";
@ -40,17 +40,16 @@ public sealed class SAV7b : SAV_BEEF, ISaveBlock7b, IGameSync, IEventFlagArray
Initialize();
}
public override bool HasPokeDex => true;
private void Initialize()
{
Box = Blocks.GetBlockOffset(BelugaBlockIndex.PokeListPokemon);
Party = Blocks.GetBlockOffset(BelugaBlockIndex.PokeListPokemon);
PokeDex = Blocks.GetBlockOffset(BelugaBlockIndex.Zukan);
WondercardData = Blocks.GiftRecords.Offset;
Box = Blocks.BlockInfo[(int)BelugaBlockIndex.PokeListPokemon].Offset;
Party = Blocks.BlockInfo[(int)BelugaBlockIndex.PokeListPokemon].Offset;
}
// Save Block accessors
public MyItem Items => Blocks.Items;
public MyItem7b Items => Blocks.Items;
public Coordinates7b Coordinates => Blocks.Coordinates;
public Misc7b Misc => Blocks.Misc;
public Zukan7b Zukan => Blocks.Zukan;
@ -60,6 +59,7 @@ public sealed class SAV7b : SAV_BEEF, ISaveBlock7b, IGameSync, IEventFlagArray
public EventWork7b EventWork => Blocks.EventWork;
public PokeListHeader Storage => Blocks.Storage;
public WB7Records GiftRecords => Blocks.GiftRecords;
public Daycare7b Daycare => Blocks.Daycare;
public CaptureRecords Captured => Blocks.Captured;
public GoParkStorage Park => Blocks.Park;
public PlayerGeoLocation7b PlayerGeoLocation => Blocks.PlayerGeoLocation;
@ -80,9 +80,6 @@ public sealed class SAV7b : SAV_BEEF, ISaveBlock7b, IGameSync, IEventFlagArray
public override int MaxEV => EffortValues.Max252;
public override int MaxStringLengthOT => 12;
public override int MaxStringLengthNickname => 12;
protected override int GiftCountMax => 48;
protected override int GiftFlagMax => 0x100 * 8;
public int EventFlagCount => 4160; // 0xDC0 (true max may be up to 0x7F less. 23A8 starts u64 hashvals)
public override bool HasParty => false; // handled via team slots
@ -125,9 +122,6 @@ public sealed class SAV7b : SAV_BEEF, ISaveBlock7b, IGameSync, IEventFlagArray
return result;
}
public override string GetBoxName(int box) => $"Box {box + 1}";
public override void SetBoxName(int box, ReadOnlySpan<char> value) { }
public override string GetString(ReadOnlySpan<byte> data) => StringConverter8.GetString(data);
public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, int maxLength, StringConverterOption option)
@ -151,24 +145,7 @@ public sealed class SAV7b : SAV_BEEF, ISaveBlock7b, IGameSync, IEventFlagArray
public override int PlayedMinutes { get => Blocks.Played.PlayedMinutes; set => Blocks.Played.PlayedMinutes = value; }
public override int PlayedSeconds { get => Blocks.Played.PlayedSeconds; set => Blocks.Played.PlayedSeconds = value; }
/// <summary>
/// Gets the <see cref="bool"/> status of a desired Event Flag
/// </summary>
/// <param name="flagNumber">Event Flag to check</param>
/// <returns>Flag is Set (true) or not Set (false)</returns>
public bool GetEventFlag(int flagNumber) => Blocks.EventWork.GetFlag(flagNumber);
/// <summary>
/// Sets the <see cref="bool"/> status of a desired Event Flag
/// </summary>
/// <param name="flagNumber">Event Flag to check</param>
/// <param name="value">Event Flag status to set</param>
/// <remarks>Flag is Set (true) or not Set (false)</remarks>
public void SetEventFlag(int flagNumber, bool value) => Blocks.EventWork.SetFlag(flagNumber, value);
protected override bool[] MysteryGiftReceivedFlags { get => Blocks.GiftRecords.GetFlags(); set => Blocks.GiftRecords.SetFlags(value); }
protected override DataMysteryGift[] MysteryGiftCards { get => Blocks.GiftRecords.GetRecords(); set => Blocks.GiftRecords.SetRecords((WR7[])value); }
public int GameSyncIDSize => MyStatus7b.GameSyncIDSize; // 64 bits
public string GameSyncID { get => Blocks.Status.GameSyncID; set => Blocks.Status.GameSyncID = value; }
IMysteryGiftStorage IMysteryGiftStorageProvider.MysteryGiftStorage => Blocks.GiftRecords;
}

View file

@ -8,7 +8,7 @@ namespace PKHeX.Core;
/// <summary>
/// Generation 8 <see cref="SaveFile"/> object for <see cref="GameVersion.BDSP"/> games.
/// </summary>
public sealed class SAV8BS : SaveFile, ISaveFileRevision, ITrainerStatRecord, IEventFlagArray, IEventWorkArray<int>
public sealed class SAV8BS : SaveFile, ISaveFileRevision, ITrainerStatRecord, IEventWorkArray<int>, IBoxDetailName, IBoxDetailWallpaper, IDaycareStorage, IDaycareEggState, IDaycareRandomState<ulong>
{
// Save Data Attributes
protected internal override string ShortSummary => $"{OT} ({Version}) - {System.LastSavedTime}";
@ -20,58 +20,61 @@ public sealed class SAV8BS : SaveFile, ISaveFileRevision, ITrainerStatRecord, IE
return gen <= 8;
});
public SAV8BS() : this(new byte[SaveUtil.SIZE_G8BDSP_3], false) => SaveRevision = (int)Gem8Version.V1_3;
public SAV8BS(byte[] data, bool exportable = true) : base(data, exportable)
{
FlagWork = new FlagWork8b(this, 0x00004);
Items = new MyItem8b(this, 0x0563C);
Underground = new UndergroundItemList8b(this, 0x111BC);
SelectBoundItems = new SaveItemShortcut8b(this, 0x14090); // size: 0x8
PartyInfo = new Party8b(this, 0x14098);
BoxLayout = new BoxLayout8b(this, 0x148AA); // size: 0x64A
var Raw = Data.AsMemory();
FlagWork = new FlagWork8b(this, Raw.Slice(0x00004, FlagWork8b.SIZE));
Items = new MyItem8b(this, Raw.Slice(0x0563C, MyItem8b.SIZE));
Underground = new UndergroundItemList8b(this, Raw.Slice(0x111BC, UndergroundItemList8b.SIZE));
SelectBoundItems = new SaveItemShortcut8b(this, Raw.Slice(0x14090, 8));
PartyInfo = new Party8b(this, Raw.Slice(0x14098, Party8b.SIZE));
BoxLayout = new BoxLayout8b(this, Raw.Slice(0x148AA, 0x64A));
// 0x14EF4 - Box[40]
// PLAYER_DATA:
Config = new ConfigSave8b(this, 0x79B74); // size: 0x40
MyStatus = new MyStatus8b(this, 0x79BB4); // size: 0x50
Played = new PlayTime8b(this, 0x79C04); // size: 0x04
Contest = new Contest8b(this, 0x79C08); // size: 0x720
Config = new ConfigSave8b(this, Raw.Slice(0x79B74, 0x40));
MyStatus = new MyStatus8b(this, Raw.Slice(0x79BB4, 0x50));
Played = new PlayTime8b(this, Raw.Slice(0x79C04, 0x04));
Contest = new Contest8b(this, Raw.Slice(0x79C08, 0x720));
Zukan = new Zukan8b(this, 0x7A328); // size: 0x30B8
BattleTrainer = new BattleTrainerStatus8b(this, 0x7D3E0); // size: 0x1618
MenuSelection = new MenuSelect8b(this, 0x7E9F8); // size: 0x44
FieldObjects = new FieldObjectSave8b(this, 0x7EA3C); // size: 0x109A0 (1000 * 0x44)
Records = new Record8b(this, 0x8F3DC); // size: 0x78 * 12
Encounter = new EncounterSave8b(this, 0x8F97C); // size: 0x188
Player = new PlayerData8b(this, 0x8FB04); // 0x80
SealDeco = new SealBallDecoData8b(this, 0x8FB84); // size: 0x4288
SealList = new SealList8b(this, 0x93E0C); // size: 0x960 SaveSealData[200]
Random = new RandomGroup8b(this, 0x9476C); // size: 0x630
FieldGimmick = new FieldGimmickSave8b(this, 0x94D9C); // FieldGimmickSaveData; int[3] gearRotate
BerryTrees = new BerryTreeGrowSave8b(this, 0x94DA8); // size: 0x808
Poffins = new PoffinSaveData8b(this, 0x955B0); // size: 0x644
BattleTower = new BattleTowerWork8b(this, 0x95BF4); // size: 0x1B8
System = new SystemData8b(this, 0x95DAC); // size: 0x138
Poketch = new Poketch8b(this, 0x95EE4); // todo
Daycare = new Daycare8b(this, 0x96080); // 0x2C0
Zukan = new Zukan8b(this, Raw.Slice(0x7A328, 0x30B8));
BattleTrainer = new BattleTrainerStatus8b(this, Raw.Slice(0x7D3E0, 0x1618));
MenuSelection = new MenuSelect8b(this, Raw.Slice(0x7E9F8, 0x44));
FieldObjects = new FieldObjectSave8b(this, Raw.Slice(0x7EA3C, 0x109A0));
Records = new Record8b(this, Raw.Slice(0x8F3DC, 0x78 * 12));
Encounter = new EncounterSave8b(this, Raw.Slice(0x8F97C, 0x188));
Player = new PlayerData8b(this, Raw.Slice(0x8FB04, PlayerData8b.SIZE));
SealDeco = new SealBallDecoData8b(this, Raw.Slice(0x8FB84, SealBallDecoData8b.SIZE));
SealList = new SealList8b(this, Raw.Slice(0x93E0C, 0x960)); // size: 0x960 SaveSealData[200]
Random = new RandomGroup8b(this, Raw.Slice(0x9476C, 0x630)); // size: 0x630
FieldGimmick = new FieldGimmickSave8b(this, Raw.Slice(0x94D9C, 0xC)); // FieldGimmickSaveData; int[3] gearRotate
BerryTrees = new BerryTreeGrowSave8b(this, Raw.Slice(0x94DA8, 0x808)); // size: 0x808
Poffins = new PoffinSaveData8b(this, Raw.Slice(0x955B0, 0x644)); // size: 0x644
BattleTower = new BattleTowerWork8b(this, Raw.Slice(0x95BF4, 0x1B8)); // size: 0x1B8
System = new SystemData8b(this, Raw.Slice(0x95DAC, SystemData8b.SIZE));
Poketch = new Poketch8b(this, Raw.Slice(0x95EE4, Poketch8b.SIZE));
Daycare = new Daycare8b(this, Raw.Slice(0x96080, Daycare8b.SIZE));
// 0x96340 - _DENDOU_SAVEDATA; DENDOU_RECORD[30], POKEMON_DATA_INSIDE[6], ushort[4] ?
// BadgeSaveData; byte[8]
// BoukenNote; byte[24]
// TV_DATA (int[48], TV_STR_DATA[42]), (int[37], bool[37])*2, (int[8], int[8]), TV_STR_DATA[10]; 144 128bit zeroed (900 bytes?)?
UgSaveData = new UgSaveData8b(this, 0x9A89C); // size: 0x27A0
UgSaveData = new UgSaveData8b(this, Raw.Slice(0x9A89C, 0x27A0));
// 0x9D03C - GMS_DATA // size: 0x31304, (GMS_POINT_DATA[650], ushort, ushort, byte)?; substructure GMS_POINT_HISTORY_DATA[5]
// 0xCE340 - PLAYER_NETWORK_DATA; bcatFlagArray byte[1300]
UnionSave = new UnionSaveData8b(this, 0xCEA10); // size: 0xC
ContestPhotoLanguage = new ContestPhotoLanguage8b(this, 0xCEA1C); // size: 0x18
ZukanExtra = new ZukanSpinda8b(this, 0xCEA34); // size: 0x64 (100)
UnionSave = new UnionSaveData8b(this, Raw.Slice(0xCEA10, 0xC));
ContestPhotoLanguage = new ContestPhotoLanguage8b(this, Raw.Slice(0xCEA1C, 0x18));
ZukanExtra = new ZukanSpinda8b(this, Raw.Slice(0xCEA34, 0x64));
// CON_PHOTO_EXT_DATA[5]
// GMS_POINT_HISTORY_EXT_DATA[3250]
UgCount = new UgCountRecord8b(this, 0xE8178); // size: 0x20
UgCount = new UgCountRecord8b(this, Raw.Slice(0xE8178, 0x20)); // size: 0x20
// 0xE8198 - ReBuffnameData; RE_DENDOU_RECORD[30], RE_DENDOU_POKEMON_DATA_INSIDE[6] (0x20) = 0x1680
// 0xE9818 -- 0x10 byte[] MD5 hash of all savedata;
// v1.1 additions
RecordAdd = new RecordAddData8b(this, 0xE9828); // size: 0x3C0
MysteryRecords = new MysteryBlock8b(this, 0xE9BE8); // size: ???
RecordAdd = new RecordAddData8b(this, GetSafe(Raw, 0xE9828, 0x3C0));
MysteryRecords = new MysteryBlock8b(this, GetSafe(Raw, 0xE9BE8, MysteryBlock8b.MinSize)); // size: ???
// POKETCH_POKETORE_COUNT_ARRAY -- (u16 species, u16 unused, i32 count, i32 reserved, i32 reserved)[3] = 0x10bytes
// PLAYREPORT_DATA -- reporting player progress online? 248 bytes?
// MT_DATA mtData; -- 0x400 bytes
@ -85,14 +88,19 @@ public sealed class SAV8BS : SaveFile, ISaveFileRevision, ITrainerStatRecord, IE
Initialize();
}
public SAV8BS() : this(new byte[SaveUtil.SIZE_G8BDSP_3], false) => SaveRevision = (int)Gem8Version.V1_3;
private static Memory<byte> GetSafe(Memory<byte> src, int ofs, int len)
{
if (ofs + len > src.Length)
return new byte[len];
return src.Slice(ofs, len);
}
public override bool HasPokeDex => true;
private void Initialize()
{
Box = 0x14EF4;
Party = PartyInfo.Offset;
PokeDex = Zukan.PokeDex;
DaycareOffset = Daycare.Offset;
Party = 1;
ReloadBattleTeams();
TeamSlots = BoxLayout.TeamSlots;
@ -243,10 +251,6 @@ public sealed class SAV8BS : SaveFile, ISaveFileRevision, ITrainerStatRecord, IE
return StringConverter8.SetString(destBuffer, value, maxLength, option);
}
public int EventFlagCount => FlagWork8b.COUNT_FLAG;
public bool GetEventFlag(int flagNumber) => FlagWork.GetFlag(flagNumber);
public void SetEventFlag(int flagNumber, bool value) => FlagWork.SetFlag(flagNumber, value);
// Player Information
public override uint ID32 { get => MyStatus.ID32; set => MyStatus.ID32 = value; }
public override ushort TID16 { get => MyStatus.TID16; set => MyStatus.TID16 = value; }
@ -267,11 +271,10 @@ public sealed class SAV8BS : SaveFile, ISaveFileRevision, ITrainerStatRecord, IE
// Storage
public override int GetPartyOffset(int slot) => Party + (SIZE_PARTY * slot);
public override int GetBoxOffset(int box) => Box + (SIZE_PARTY * box * 30);
protected override int GetBoxWallpaperOffset(int box) => BoxLayout.GetBoxWallpaperOffset(box);
public override int GetBoxWallpaper(int box) => BoxLayout.GetBoxWallpaper(box);
public override void SetBoxWallpaper(int box, int value) => BoxLayout.SetBoxWallpaper(box, value);
public override string GetBoxName(int box) => BoxLayout[box];
public override void SetBoxName(int box, ReadOnlySpan<char> value) => BoxLayout.SetBoxName(box, value);
public int GetBoxWallpaper(int box) => BoxLayout.GetBoxWallpaper(box);
public void SetBoxWallpaper(int box, int value) => BoxLayout.SetBoxWallpaper(box, value);
public string GetBoxName(int box) => BoxLayout[box];
public void SetBoxName(int box, ReadOnlySpan<char> value) => BoxLayout.SetBoxName(box, value);
public override byte[] GetDataForBox(PKM pk) => pk.EncryptedPartyData;
public override int CurrentBox { get => BoxLayout.CurrentBox; set => BoxLayout.CurrentBox = (byte)value; }
public override int BoxesUnlocked { get => BoxLayout.BoxesUnlocked; set => BoxLayout.BoxesUnlocked = (byte)value; }
@ -344,21 +347,17 @@ public sealed class SAV8BS : SaveFile, ISaveFileRevision, ITrainerStatRecord, IE
public int RecordCount => Record8b.RecordCount;
public int GetRecord(int recordID) => Records.GetRecord(recordID);
public int GetRecordOffset(int recordID) => Records.GetRecordOffset(recordID);
public int GetRecordOffset(int recordID) => Record8b.GetRecordOffset(recordID);
public int GetRecordMax(int recordID) => Record8b.GetMax(recordID);
public void SetRecord(int recordID, int value) => Records.SetRecord(recordID, value);
#region Daycare
public override int DaycareSeedSize => 16; // 8byte
public override int GetDaycareSlotOffset(int loc, int slot) => Daycare.GetParentSlotOffset(slot);
public override uint? GetDaycareEXP(int loc, int slot) => 0;
public override bool? IsDaycareOccupied(int loc, int slot) => Daycare.GetDaycareSlotOccupied(slot);
public override bool? IsDaycareHasEgg(int loc) => Daycare.IsEggAvailable;
public override void SetDaycareEXP(int loc, int slot, uint EXP) { }
public override void SetDaycareOccupied(int loc, int slot, bool occupied) { }
public override void SetDaycareHasEgg(int loc, bool hasEgg) => Daycare.IsEggAvailable = hasEgg;
public override string GetDaycareRNGSeed(int loc) => $"{Daycare.DaycareSeed:X16}";
public override void SetDaycareRNGSeed(int loc, string seed) => Daycare.DaycareSeed = Util.GetHexValue64(seed);
public int DaycareSlotCount => Daycare.DaycareSlotCount;
public bool IsDaycareOccupied(int slot) => Daycare.IsDaycareOccupied(slot);
public bool IsEggAvailable { get => Daycare.IsEggAvailable; set => Daycare.IsEggAvailable = value; }
public void SetDaycareOccupied(int slot, bool occupied) { }
public Memory<byte> GetDaycareSlot(int index) => Daycare.GetDaycareSlot(index);
ulong IDaycareRandomState<ulong>.Seed { get => Daycare.Seed; set => Daycare.Seed = value; }
#endregion
public int EventWorkCount => FlagWork8b.COUNT_WORK;

View file

@ -6,7 +6,7 @@ namespace PKHeX.Core;
/// <summary>
/// Generation 8 <see cref="SaveFile"/> object for <see cref="GameVersion.PLA"/> games.
/// </summary>
public sealed class SAV8LA : SaveFile, ISaveBlock8LA, ISCBlockArray, ISaveFileRevision
public sealed class SAV8LA : SaveFile, ISaveBlock8LA, ISCBlockArray, ISaveFileRevision, IBoxDetailName
{
protected internal override string ShortSummary => $"{OT} ({Version}) - {LastSaved.LastSavedTime}";
public override string Extension => string.Empty;
@ -130,11 +130,11 @@ public sealed class SAV8LA : SaveFile, ISaveBlock8LA, ISCBlockArray, ISaveFileRe
protected override Span<byte> BoxBuffer => BoxInfo.Data;
protected override Span<byte> PartyBuffer => PartyInfo.Data;
public override bool HasPokeDex => true;
private void Initialize()
{
Box = 0;
Party = 0;
PokeDex = 0;
}
public override int GetPartyOffset(int slot) => Party + (SIZE_PARTY * slot);
@ -184,8 +184,6 @@ public sealed class SAV8LA : SaveFile, ISaveBlock8LA, ISCBlockArray, ISaveFileRe
public override IReadOnlyList<InventoryPouch> Inventory { get => Items.Inventory; set => Items.Inventory = value; }
#region Boxes
public override bool HasBoxWallpapers => false;
public override bool HasNamableBoxes => true;
public override int CurrentBox { get => BoxLayout.CurrentBox; set => BoxLayout.CurrentBox = value; }
public override int BoxesUnlocked { get => (byte)Blocks.GetBlockValue(SaveBlockAccessor8LA.KBoxesUnlocked); set => Blocks.SetBlockValue(SaveBlockAccessor8LA.KBoxesUnlocked, (byte)value); }
@ -209,10 +207,10 @@ public sealed class SAV8LA : SaveFile, ISaveBlock8LA, ISCBlockArray, ISaveFileRe
}
public override int GetBoxOffset(int box) => Box + (SIZE_BOXSLOT * box * 30);
public override string GetBoxName(int box) => BoxLayout.GetBoxName(box);
public override void SetBoxName(int box, ReadOnlySpan<char> value) => BoxLayout.SetBoxName(box, value);
public string GetBoxName(int box) => BoxLayout[box];
public void SetBoxName(int box, ReadOnlySpan<char> value) => BoxLayout.SetBoxName(box, value);
public override int GetBoxWallpaper(int box)
public int GetBoxWallpaper(int box)
{
if ((uint)box >= BoxCount)
return box;
@ -220,7 +218,7 @@ public sealed class SAV8LA : SaveFile, ISaveBlock8LA, ISCBlockArray, ISaveFileRe
return b.Data[box];
}
public override void SetBoxWallpaper(int box, int value)
public void SetBoxWallpaper(int box, int value)
{
if ((uint)box >= BoxCount)
return;

View file

@ -6,7 +6,7 @@ namespace PKHeX.Core;
/// <summary>
/// Generation 8 <see cref="SaveFile"/> object for <see cref="GameVersion.SWSH"/> games.
/// </summary>
public sealed class SAV8SWSH : SaveFile, ISaveBlock8SWSH, ITrainerStatRecord, ISaveFileRevision, ISCBlockArray
public sealed class SAV8SWSH : SaveFile, ISaveBlock8SWSH, ITrainerStatRecord, ISaveFileRevision, ISCBlockArray, IBoxDetailName, IBoxDetailWallpaper
{
public SAV8SWSH(byte[] data) : this(SwishCrypto.Decrypt(data)) { }
@ -99,11 +99,11 @@ public sealed class SAV8SWSH : SaveFile, ISaveBlock8SWSH, ITrainerStatRecord, IS
public override GameVersion MaxGameID => Legal.MaxGameID_8;
public override int MaxAbilityID => m_abil;
public override bool HasPokeDex => true;
private void Initialize()
{
Box = 0;
Party = 0;
PokeDex = 0;
TeamIndexes.LoadBattleTeams();
int rev = SaveRevision;
@ -183,8 +183,8 @@ public sealed class SAV8SWSH : SaveFile, ISaveBlock8SWSH, ITrainerStatRecord, IS
// Storage
public override int GetPartyOffset(int slot) => Party + (SIZE_PARTY * slot);
public override int GetBoxOffset(int box) => Box + (SIZE_PARTY * box * 30);
public override string GetBoxName(int box) => BoxLayout[box];
public override void SetBoxName(int box, ReadOnlySpan<char> value) => BoxLayout.SetBoxName(box, value);
public string GetBoxName(int box) => BoxLayout[box];
public void SetBoxName(int box, ReadOnlySpan<char> value) => BoxLayout.SetBoxName(box, value);
public override byte[] GetDataForBox(PKM pk) => pk.EncryptedPartyData;
protected override void SetPKM(PKM pk, bool isParty = false)
@ -246,7 +246,7 @@ public sealed class SAV8SWSH : SaveFile, ISaveBlock8SWSH, ITrainerStatRecord, IS
protected override Span<byte> BoxBuffer => BoxInfo.Data;
protected override Span<byte> PartyBuffer => PartyInfo.Data;
public override PK8 GetDecryptedPKM(byte[] data) => GetPKM(DecryptPKM(data));
public override PK8 GetBoxSlot(int offset) => GetDecryptedPKM(BoxInfo.Data.AsSpan(offset, SIZE_PARTY).ToArray()); // party format in boxes!
public override PK8 GetBoxSlot(int offset) => GetDecryptedPKM(BoxInfo.Data.Slice(offset, SIZE_PARTY).ToArray()); // party format in boxes!
public int GetRecord(int recordID) => Records.GetRecord(recordID);
public void SetRecord(int recordID, int value) => Records.SetRecord(recordID, value);
@ -285,10 +285,7 @@ public sealed class SAV8SWSH : SaveFile, ISaveBlock8SWSH, ITrainerStatRecord, IS
}
}
public override bool HasBoxWallpapers => true;
public override bool HasNamableBoxes => true;
public override int GetBoxWallpaper(int box)
public int GetBoxWallpaper(int box)
{
if ((uint)box >= BoxCount)
return box;
@ -296,7 +293,7 @@ public sealed class SAV8SWSH : SaveFile, ISaveBlock8SWSH, ITrainerStatRecord, IS
return b.Data[box];
}
public override void SetBoxWallpaper(int box, int value)
public void SetBoxWallpaper(int box, int value)
{
if ((uint)box >= BoxCount)
return;

View file

@ -7,7 +7,7 @@ namespace PKHeX.Core;
/// <summary>
/// Generation 9 <see cref="SaveFile"/> object for <see cref="GameVersion.SV"/> games.
/// </summary>
public sealed class SAV9SV : SaveFile, ISaveBlock9Main, ISCBlockArray, ISaveFileRevision
public sealed class SAV9SV : SaveFile, ISaveBlock9Main, ISCBlockArray, ISaveFileRevision, IBoxDetailName, IBoxDetailWallpaper
{
protected internal override string ShortSummary => $"{OT} ({Version}) - {LastSaved.DisplayValue}";
public override string Extension => string.Empty;
@ -104,11 +104,12 @@ public sealed class SAV9SV : SaveFile, ISaveBlock9Main, ISCBlockArray, ISaveFile
public override int MaxItemID => m_item;
public override int MaxAbilityID => m_abil;
public override bool HasPokeDex => true;
private void Initialize()
{
Box = 0;
Party = 0;
PokeDex = 0;
TeamIndexes.LoadBattleTeams();
int rev = SaveRevision;
@ -189,8 +190,8 @@ public sealed class SAV9SV : SaveFile, ISaveBlock9Main, ISCBlockArray, ISaveFile
// Storage
public override int GetPartyOffset(int slot) => Party + (SIZE_PARTY * slot);
public override int GetBoxOffset(int box) => Box + (SIZE_PARTY * box * 30);
public override string GetBoxName(int box) => BoxLayout[box];
public override void SetBoxName(int box, ReadOnlySpan<char> value) => BoxLayout.SetBoxName(box, value);
public string GetBoxName(int box) => BoxLayout[box];
public void SetBoxName(int box, ReadOnlySpan<char> value) => BoxLayout.SetBoxName(box, value);
public override byte[] GetDataForBox(PKM pk) => pk.EncryptedPartyData;
protected override void SetPKM(PKM pk, bool isParty = false)
@ -254,7 +255,7 @@ public sealed class SAV9SV : SaveFile, ISaveBlock9Main, ISCBlockArray, ISaveFile
protected override Span<byte> BoxBuffer => BoxInfo.Data;
protected override Span<byte> PartyBuffer => PartyInfo.Data;
public override PK9 GetDecryptedPKM(byte[] data) => GetPKM(DecryptPKM(data));
public override PK9 GetBoxSlot(int offset) => GetDecryptedPKM(BoxInfo.Data.AsSpan(offset, SIZE_PARTY).ToArray()); // party format in boxes!
public override PK9 GetBoxSlot(int offset) => GetDecryptedPKM(BoxInfo.Data.Slice(offset, SIZE_PARTY).ToArray()); // party format in boxes!
//public int GetRecord(int recordID) => Records.GetRecord(recordID);
//public void SetRecord(int recordID, int value) => Records.SetRecord(recordID, value);
@ -277,8 +278,6 @@ public sealed class SAV9SV : SaveFile, ISaveBlock9Main, ISCBlockArray, ISaveFile
public override int CurrentBox { get => BoxLayout.CurrentBox; set => BoxLayout.CurrentBox = value; }
public override int BoxesUnlocked { get => (byte)Blocks.GetBlockValue(SaveBlockAccessor9SV.KBoxesUnlocked); set => Blocks.SetBlockValue(SaveBlockAccessor9SV.KBoxesUnlocked, (byte)value); }
public override bool HasBoxWallpapers => true;
public override bool HasNamableBoxes => true;
public Span<byte> Coordinates => Blocks.GetBlock(SaveBlockAccessor9SV.KCoordinates).Data;
public float X { get => ReadSingleLittleEndian(Coordinates); set => WriteSingleLittleEndian(Coordinates, value); }
public float Y { get => ReadSingleLittleEndian(Coordinates[4..]); set => WriteSingleLittleEndian(Coordinates[4..], value); }
@ -312,7 +311,7 @@ public sealed class SAV9SV : SaveFile, ISaveBlock9Main, ISCBlockArray, ISaveFile
RW = rw;
}
public override int GetBoxWallpaper(int box)
public int GetBoxWallpaper(int box)
{
if ((uint)box >= BoxCount)
return box;
@ -320,7 +319,7 @@ public sealed class SAV9SV : SaveFile, ISaveBlock9Main, ISCBlockArray, ISaveFile
return b.Data[box];
}
public override void SetBoxWallpaper(int box, int value)
public void SetBoxWallpaper(int box, int value)
{
if ((uint)box >= BoxCount)
return;

View file

@ -49,8 +49,6 @@ public abstract class SAV_STADIUM : SaveFile, ILangDeviantSave
protected sealed override byte[] DecryptPKM(byte[] data) => data;
public sealed override int GetPartyOffset(int slot) => -1;
public override string GetBoxName(int box) => $"Box {box + 1}";
public sealed override void SetBoxName(int box, ReadOnlySpan<char> value) { }
public sealed override bool ChecksumsValid => GetBoxChecksumsValid();
public sealed override string ChecksumInfo => ChecksumsValid ? "Checksum valid." : "Checksum invalid";
protected abstract void SetBoxChecksum(int box);

View file

@ -8,7 +8,7 @@ namespace PKHeX.Core;
/// <summary>
/// Base Class for Save Files
/// </summary>
public abstract class SaveFile : ITrainerInfo, IGameValueLimit, IBoxDetailWallpaper, IBoxDetailName, IGeneration, IVersion
public abstract class SaveFile : ITrainerInfo, IGameValueLimit, IGeneration, IVersion
{
// General Object Properties
public byte[] Data;
@ -104,7 +104,7 @@ public abstract class SaveFile : ITrainerInfo, IGameValueLimit, IBoxDetailWallpa
/// <param name="offset">Offset to read from</param>
/// <param name="bitIndex">Bit index to read</param>
/// <returns>Flag is Set (true) or not Set (false)</returns>
public virtual bool GetFlag(int offset, int bitIndex) => FlagUtil.GetFlag(Data, offset, bitIndex);
public virtual bool GetFlag(int offset, int bitIndex) => GetFlag(Data, offset, bitIndex);
/// <summary>
/// Sets the <see cref="bool"/> status of the Flag at the specified offset and index.
@ -113,29 +113,17 @@ public abstract class SaveFile : ITrainerInfo, IGameValueLimit, IBoxDetailWallpa
/// <param name="bitIndex">Bit index to read</param>
/// <param name="value">Flag status to set</param>
/// <remarks>Flag is Set (true) or not Set (false)</remarks>
public virtual void SetFlag(int offset, int bitIndex, bool value) => FlagUtil.SetFlag(Data, offset, bitIndex, value);
public virtual void SetFlag(int offset, int bitIndex, bool value) => SetFlag(Data, offset, bitIndex, value);
public bool GetFlag(Span<byte> data, int offset, int bitIndex) => FlagUtil.GetFlag(data, offset, bitIndex);
public void SetFlag(Span<byte> data, int offset, int bitIndex, bool value)
{
FlagUtil.SetFlag(data, offset, bitIndex, value);
State.Edited = true;
}
public virtual IReadOnlyList<InventoryPouch> Inventory { get => []; set { } }
#region Mystery Gift
protected virtual int GiftCountMax => int.MinValue;
protected virtual int GiftFlagMax => 0x800;
protected int WondercardData { get; set; } = int.MinValue;
public bool HasWondercards => WondercardData > -1;
protected virtual bool[] MysteryGiftReceivedFlags { get => []; set { } }
protected virtual DataMysteryGift[] MysteryGiftCards { get => []; set { } }
public virtual MysteryGiftAlbum GiftAlbum
{
get => new(MysteryGiftCards, MysteryGiftReceivedFlags);
set
{
MysteryGiftReceivedFlags = value.Flags;
MysteryGiftCards = value.Gifts;
}
}
#endregion
#region Player Info
public virtual byte Gender { get; set; }
public virtual int Language { get => -1; set { } }
@ -227,24 +215,6 @@ public abstract class SaveFile : ITrainerInfo, IGameValueLimit, IBoxDetailWallpa
// Varied Methods
protected abstract void SetChecksums();
#region Daycare
public bool HasDaycare => DaycareOffset > -1;
protected int DaycareOffset { get; set; } = int.MinValue;
public virtual int DaycareSeedSize => 0;
public int DaycareIndex;
public virtual bool HasTwoDaycares => false;
public virtual int GetDaycareSlotOffset(int loc, int slot) => -1;
public virtual uint? GetDaycareEXP(int loc, int slot) => null;
public virtual string GetDaycareRNGSeed(int loc) => string.Empty;
public virtual bool? IsDaycareHasEgg(int loc) => null;
public virtual bool? IsDaycareOccupied(int loc, int slot) => null;
public virtual void SetDaycareEXP(int loc, int slot, uint EXP) { }
public virtual void SetDaycareRNGSeed(int loc, string seed) { }
public virtual void SetDaycareHasEgg(int loc, bool hasEgg) { }
public virtual void SetDaycareOccupied(int loc, int slot, bool occupied) { }
#endregion
private Span<byte> GetPartySpan(int index) => PartyBuffer[GetPartyOffset(index)..];
public PKM GetPartySlotAtIndex(int index) => GetPartySlot(GetPartySpan(index));
@ -395,8 +365,7 @@ public abstract class SaveFile : ITrainerInfo, IGameValueLimit, IBoxDetailWallpa
#endregion
#region Pokédex
public int PokeDex { get; protected set; } = int.MinValue;
public bool HasPokeDex => PokeDex > -1;
public virtual bool HasPokeDex => false;
public virtual bool GetSeen(ushort species) => false;
public virtual void SetSeen(ushort species, bool seen) { }
public virtual bool GetCaught(ushort species) => false;
@ -612,16 +581,11 @@ public abstract class SaveFile : ITrainerInfo, IGameValueLimit, IBoxDetailWallpa
int len = BoxSlotCount * SIZE_BOXSLOT;
byte[] boxdata = storage.Slice(GetBoxOffset(0), len * BoxCount).ToArray(); // get all boxes
string[] boxNames = Get(GetBoxName, BoxCount);
int[] boxWallpapers = Get(GetBoxWallpaper, BoxCount);
static T[] Get<T>(Func<int, T> act, int count)
{
T[] result = new T[count];
for (int i = 0; i < result.Length; i++)
result[i] = act(i);
return result;
}
if (this is IBoxDetailWallpaper w)
w.MoveWallpaper(box, insertBeforeBox);
if (this is IBoxDetailName n)
n.MoveBoxName(box, insertBeforeBox);
min /= BoxSlotCount;
max /= BoxSlotCount;
@ -638,8 +602,6 @@ public abstract class SaveFile : ITrainerInfo, IGameValueLimit, IBoxDetailWallpa
}
boxdata.AsSpan(len * i, len).CopyTo(storage[GetBoxOffset(b)..]);
SetBoxName(b, boxNames[i]);
SetBoxWallpaper(b, boxWallpapers[i]);
}
SlotPointerUtil.UpdateMove(box, insertBeforeBox, BoxSlotCount, SlotPointers);
@ -670,14 +632,12 @@ public abstract class SaveFile : ITrainerInfo, IGameValueLimit, IBoxDetailWallpa
b1.CopyTo(boxData[b2o..]);
// Name
string b1n = GetBoxName(box1);
SetBoxName(box1, GetBoxName(box2));
SetBoxName(box2, b1n);
if (this is IBoxDetailName n)
n.SwapBoxName(box1, box2);
// Wallpaper
int b1w = GetBoxWallpaper(box1);
SetBoxWallpaper(box1, GetBoxWallpaper(box2));
SetBoxWallpaper(box2, b1w);
if (this is IBoxDetailWallpaper w)
w.SwapWallpaper(box1, box2);
// Pointers
SlotPointerUtil.UpdateSwap(box1, box2, BoxSlotCount, SlotPointers);
@ -809,31 +769,6 @@ public abstract class SaveFile : ITrainerInfo, IGameValueLimit, IBoxDetailWallpa
}
#endregion
#region Storage Name & Decoration
public virtual bool HasBoxWallpapers => GetBoxWallpaperOffset(0) > -1;
public virtual bool HasNamableBoxes => HasBoxWallpapers;
public abstract string GetBoxName(int box);
public abstract void SetBoxName(int box, ReadOnlySpan<char> value);
protected virtual int GetBoxWallpaperOffset(int box) => -1;
public virtual int GetBoxWallpaper(int box)
{
int offset = GetBoxWallpaperOffset(box);
if (offset < 0 || (uint)box > BoxCount)
return box;
return Data[offset];
}
public virtual void SetBoxWallpaper(int box, int value)
{
int offset = GetBoxWallpaperOffset(box);
if (offset < 0 || (uint)box > BoxCount)
return;
Data[offset] = (byte)value;
}
#endregion
#region Box Binaries
public byte[] GetPCBinary() => BoxData.SelectMany(GetDataForBox).ToArray();
public byte[] GetBoxBinary(int box) => GetBoxData(box).SelectMany(GetDataForBox).ToArray();

View file

@ -5,7 +5,7 @@ namespace PKHeX.Core;
/// <summary>
/// Generation 3 <see cref="SaveFile"/> object that reads exported data for Generation 3 PokeStock .gst dumps.
/// </summary>
public sealed class Bank3 : BulkStorage
public sealed class Bank3 : BulkStorage, IBoxDetailNameRead
{
public Bank3(byte[] data) : base(data, typeof(PK3), 0) => Version = GameVersion.RS;
@ -22,6 +22,6 @@ public sealed class Bank3 : BulkStorage
private int BoxDataSize => SlotsPerBox * SIZE_STORED;
public override int GetBoxOffset(int box) => Box + (BoxDataSize * box);
public override string GetBoxName(int box) => GetString(Data.AsSpan(GetBoxNameOffset(box), BoxNameSize));
public string GetBoxName(int box) => GetString(Data.AsSpan(GetBoxNameOffset(box), BoxNameSize));
private static int GetBoxNameOffset(int box) => 0x25800 + (9 * box);
}

View file

@ -5,7 +5,7 @@ namespace PKHeX.Core;
/// <summary>
/// Generation 4 <see cref="SaveFile"/> object that reads Generation 4 PokeStock .stk dumps.
/// </summary>
public sealed class Bank4 : BulkStorage
public sealed class Bank4 : BulkStorage, IBoxDetailNameRead
{
public Bank4(byte[] data) : base(data, typeof(PK4), 0) => Version = GameVersion.HGSS;
@ -22,6 +22,6 @@ public sealed class Bank4 : BulkStorage
private int BoxDataSize => SlotsPerBox * SIZE_STORED;
public override int GetBoxOffset(int box) => Box + (BoxDataSize * box);
public override string GetBoxName(int box) => GetString(Data.AsSpan(GetBoxNameOffset(box), BoxNameSize / 2));
public string GetBoxName(int box) => GetString(Data.AsSpan(GetBoxNameOffset(box), BoxNameSize / 2));
private static int GetBoxNameOffset(int box) => 0x3FC00 + (0x19 * box);
}

View file

@ -7,7 +7,7 @@ namespace PKHeX.Core;
/// <summary>
/// Generation 7 <see cref="SaveFile"/> object that reads from Pokémon Bank savedata (stored on AWS).
/// </summary>
public sealed class Bank7 : BulkStorage
public sealed class Bank7 : BulkStorage, IBoxDetailNameRead
{
public Bank7(byte[] data, Type t, [ConstantExpected] int start, int slotsPerBox = 30) : base(data, t, start, slotsPerBox) => Version = GameVersion.USUM;
@ -47,7 +47,7 @@ public sealed class Bank7 : BulkStorage
private int BoxDataSize => (SlotsPerBox * SIZE_STORED) + BankNameSpacing;
public override int GetBoxOffset(int box) => Box + (BoxDataSize * box);
public override string GetBoxName(int box) => GetString(Data.AsSpan(GetBoxNameOffset(box), BankNameSize / 2));
public string GetBoxName(int box) => GetString(Data.AsSpan(GetBoxNameOffset(box), BankNameSize / 2));
public int GetBoxNameOffset(int box) => GetBoxOffset(box) + (SlotsPerBox * SIZE_STORED);
public int GetBoxIndex(int box) => ReadUInt16LittleEndian(Data.AsSpan(GetBoxNameOffset(box) + BankNameSize));

View file

@ -50,8 +50,6 @@ public abstract class BulkStorage : SaveFile
protected override void SetChecksums() { }
public override int GetBoxOffset(int box) => Box + (box * (SlotsPerBox * SIZE_STORED));
public override string GetBoxName(int box) => $"Box {box + 1:d2}";
public sealed override void SetBoxName(int box, ReadOnlySpan<char> value) { }
public sealed override int GetPartyOffset(int slot) => int.MinValue;
public override string GetString(ReadOnlySpan<byte> data)

View file

@ -0,0 +1,6 @@
namespace PKHeX.Core;
public interface IDaycareEggState
{
bool IsEggAvailable { get; set; }
}

View file

@ -0,0 +1,7 @@
namespace PKHeX.Core;
public interface IDaycareExperience
{
uint GetDaycareEXP(int index);
void SetDaycareEXP(int index, uint value);
}

View file

@ -0,0 +1,8 @@
namespace PKHeX.Core;
public interface IDaycareMulti
{
int DaycareCount { get; }
IDaycareStorage this[int index] { get; }
}

View file

@ -0,0 +1,6 @@
namespace PKHeX.Core;
public interface IDaycareRandomState<T>
{
T Seed { get; set; }
}

View file

@ -0,0 +1,16 @@
using System;
namespace PKHeX.Core;
public interface IDaycareStorage
{
int DaycareSlotCount { get; }
/// <summary>
/// Gets the segment of memory that holds a stored entity.
/// </summary>
Memory<byte> GetDaycareSlot(int index);
bool IsDaycareOccupied(int index);
void SetDaycareOccupied(int index, bool occupied);
}

View file

@ -5,62 +5,63 @@ namespace PKHeX.Core;
public sealed class Roamer3 : IContestStats
{
private readonly int Offset;
public const int SIZE = 0x14;
public bool IsGlitched { get; }
private readonly byte[] Data;
private readonly SAV3 SAV;
private readonly Memory<byte> Raw;
private Span<byte> Data => Raw.Span;
public Roamer3(SAV3 sav)
{
Data = (SAV = sav).Large;
Offset = sav.Version switch
var buffer = sav.Large;
var offset = sav.Version switch
{
GameVersion.RS => 0x3144,
GameVersion.E => 0x31DC,
_ => 0x30D0, // FRLG
};
Raw = buffer.AsMemory(offset, SIZE);
IsGlitched = sav.Version != GameVersion.E;
}
public uint IV32
{
get => ReadUInt32LittleEndian(Data.AsSpan(Offset));
set => WriteUInt32LittleEndian(Data.AsSpan(Offset), value);
get => ReadUInt32LittleEndian(Data);
set => WriteUInt32LittleEndian(Data, value);
}
public uint PID
{
get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 4));
set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 4), value);
get => ReadUInt32LittleEndian(Data[4..]);
set => WriteUInt32LittleEndian(Data[4..], value);
}
public ushort Species
{
get => SpeciesConverter.GetNational3(ReadUInt16LittleEndian(Data.AsSpan(Offset + 8)));
set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 8), SpeciesConverter.GetInternal3(value));
get => SpeciesConverter.GetNational3(ReadUInt16LittleEndian(Data[8..]));
set => WriteUInt16LittleEndian(Data[8..], SpeciesConverter.GetInternal3(value));
}
public int HP_Current
{
get => ReadInt16LittleEndian(Data.AsSpan(Offset + 10));
set => WriteInt16LittleEndian(Data.AsSpan(Offset + 10), (short)value);
get => ReadInt16LittleEndian(Data[10..]);
set => WriteInt16LittleEndian(Data[10..], (short)value);
}
public byte CurrentLevel
{
get => Data[Offset + 12];
set => Data[Offset + 12] = value;
get => Data[12];
set => Data[12] = value;
}
public int Status { get => Data[Offset + 0x0D]; set => Data[Offset + 0x0D] = (byte)value; }
public int Status { get => Data[0x0D]; set => Data[0x0D] = (byte)value; }
public byte ContestCool { get => Data[Offset + 0x0E]; set => Data[Offset + 0x0E] = value; }
public byte ContestBeauty { get => Data[Offset + 0x0F]; set => Data[Offset + 0x0F] = value; }
public byte ContestCute { get => Data[Offset + 0x10]; set => Data[Offset + 0x10] = value; }
public byte ContestSmart { get => Data[Offset + 0x11]; set => Data[Offset + 0x11] = value; }
public byte ContestTough { get => Data[Offset + 0x12]; set => Data[Offset + 0x12] = value; }
public byte ContestCool { get => Data[0x0E]; set => Data[0x0E] = value; }
public byte ContestBeauty { get => Data[0x0F]; set => Data[0x0F] = value; }
public byte ContestCute { get => Data[0x10]; set => Data[0x10] = value; }
public byte ContestSmart { get => Data[0x11]; set => Data[0x11] = value; }
public byte ContestTough { get => Data[0x12]; set => Data[0x12] = value; }
public byte ContestSheen { get => 0; set { } }
public bool Active { get => Data[Offset + 0x13] == 1; set => Data[Offset + 0x13] = value ? (byte)1 : (byte)0; }
public bool Active { get => Data[0x13] == 1; set => Data[0x13] = value ? (byte)1 : (byte)0; }
// Derived Properties
private int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 00)) | (uint)((value > 31 ? 31 : value) << 00); }
@ -92,13 +93,14 @@ public sealed class Roamer3 : IContestStats
}
/// <summary>
/// Indicates if the Roamer is shiny with the <see cref="SAV"/>'s Trainer Details.
/// Indicates if the Roamer is shiny with the <see cref="tr"/>'s Trainer Details.
/// </summary>
/// <param name="pid">PID to check for</param>
/// <param name="tr">Trainer to check for</param>
/// <returns>Indication if the PID is shiny for the trainer.</returns>
public bool IsShiny(uint pid)
public static bool IsShiny(uint pid, ITrainerID32 tr)
{
var tmp = SAV.ID32 ^ pid;
var tmp = tr.ID32 ^ pid;
var xor = (tmp >> 16) ^ (tmp & 0xFFFF);
return xor < 8;
}

View file

@ -3,61 +3,63 @@ using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
public sealed class SecretBase3(byte[] Data, int Offset)
public sealed class SecretBase3(Memory<byte> raw)
{
private Span<byte> Data => raw.Span;
private bool Japanese => Language == (int) LanguageID.Japanese;
public int SecretBaseLocation { get => Data[Offset + 0]; set => Data[Offset + 0] = (byte) value; }
public int SecretBaseLocation { get => Data[0]; set => Data[0] = (byte) value; }
public byte OriginalTrainerGender
{
get => (byte)((Data[Offset + 1] >> 4) & 1);
set => Data[Offset + 1] = (byte) ((Data[Offset + 1] & 0xEF) | ((value & 1) << 4));
get => (byte)((Data[1] >> 4) & 1);
set => Data[1] = (byte) ((Data[1] & 0xEF) | ((value & 1) << 4));
}
public bool BattledToday
{
get => ((Data[Offset + 1] >> 5) & 1) == 1;
set => Data[Offset + 1] = (byte)((Data[Offset + 1] & 0xDF) | ((value ? 1 : 0) << 5));
get => ((Data[1] >> 5) & 1) == 1;
set => Data[1] = (byte)((Data[1] & 0xDF) | ((value ? 1 : 0) << 5));
}
public int RegistryStatus
{
get => (Data[Offset + 1] >> 6) & 3;
set => Data[Offset + 1] = (byte)((Data[Offset + 1] & 0x3F) | ((value & 3) << 6));
get => (Data[1] >> 6) & 3;
set => Data[1] = (byte)((Data[1] & 0x3F) | ((value & 3) << 6));
}
public string OriginalTrainerName
{
get => StringConverter3.GetString(Data.AsSpan(Offset + 2, 7), Japanese);
set => StringConverter3.SetString(Data.AsSpan(Offset + 2, 7), value, 7, Japanese, StringConverterOption.ClearFF);
get => StringConverter3.GetString(Data.Slice(2, 7), Japanese);
set => StringConverter3.SetString(Data.Slice(2, 7), value, 7, Japanese, StringConverterOption.ClearFF);
}
public uint OT_ID
{
get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 9));
set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 9), value);
get => ReadUInt32LittleEndian(Data[9..]);
set => WriteUInt32LittleEndian(Data[9..], value);
}
public int OT_Class => Data[Offset + 9] % 5;
public int Language { get => Data[Offset + 0x0D]; set => Data[Offset + 0x0D] = (byte)value; }
public int OT_Class => Data[9] % 5;
public int Language { get => Data[0x0D]; set => Data[0x0D] = (byte)value; }
public ushort SecretBasesReceived
{
get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x0E));
set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x0E), value);
get => ReadUInt16LittleEndian(Data[0x0E..]);
set => WriteUInt16LittleEndian(Data[0x0E..], value);
}
public byte TimesEntered { get => Data[Offset + 0x10]; set => Data[Offset + 0x10] = value; }
public int Unused11 { get => Data[Offset + 0x11]; set => Data[Offset + 0x11] = (byte)value; } // alignment padding
public byte TimesEntered { get => Data[0x10]; set => Data[0x10] = value; }
public int Unused11 { get => Data[0x11]; set => Data[0x11] = (byte)value; } // alignment padding
public Span<byte> GetDecorations() => Data.AsSpan(Offset + 0x12, 0x10);
public Span<byte> GetDecorations() => Data.Slice(0x12, 0x10);
public void SetDecorations(Span<byte> value) => value.CopyTo(GetDecorations());
public Span<byte> GetDecorationCoordinates() => Data.AsSpan(Offset + 0x22, 0x10);
public Span<byte> GetDecorationCoordinates() => Data.Slice(0x22, 0x10);
public void SetDecorationCoordinates(Span<byte> value) => value.CopyTo(GetDecorationCoordinates());
private Span<byte> TeamData => Data.AsSpan(Offset + 50, 72);
private Span<byte> TeamData => Data.Slice(50, 72);
public SecretBase3Team Team
{
get => new(TeamData.ToArray());

View file

@ -6,15 +6,15 @@ namespace PKHeX.Core;
/// <summary>
/// Generation 4 Chatter Recording
/// </summary>
public sealed class Chatter4(SAV4 SAV, int offset) : SaveBlock<SAV4>(SAV, offset), IChatter
public sealed class Chatter4(SAV4 SAV, Memory<byte> raw) : SaveBlock<SAV4>(SAV, raw), IChatter
{
public bool Initialized
{
get => ReadUInt32LittleEndian(SAV.General[Offset..]) == 1u;
set => WriteUInt32LittleEndian(SAV.General[Offset..], value ? 1u : 0u);
get => ReadUInt32LittleEndian(Data) == 1u;
set => WriteUInt32LittleEndian(Data, value ? 1u : 0u);
}
public Span<byte> Recording => SAV.General.Slice(Offset + sizeof(uint), IChatter.SIZE_PCM);
public Span<byte> Recording => Data.Slice(sizeof(uint), IChatter.SIZE_PCM);
public int ConfusionChance => !Initialized ? 1 : (sbyte)Recording[15] switch
{

View file

@ -3,7 +3,7 @@ using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
public sealed class Dendou4
public sealed class Dendou4(Memory<byte> raw)
{
private const int SIZE = 0x2AB0;
private const int SIZE_FOOTER = 0x10;
@ -12,10 +12,7 @@ public sealed class Dendou4
public const int MaxClears = 9999;
public const int MaxRecords = 30;
private readonly byte[] Raw;
private readonly int Offset;
private Span<byte> Data => Raw.AsSpan(Offset, SIZE_BLOCK);
public Dendou4(byte[] data, int offset) => (Raw, Offset) = (data, offset);
private Span<byte> Data => raw.Span;
// Structure:
// record[30] records

View file

@ -0,0 +1,23 @@
using System;
namespace PKHeX.Core;
public sealed class BattleBox5(SAV5 sav, Memory<byte> raw) : SaveBlock<SAV5>(sav, raw)
{
private const int SizeStored = PokeCrypto.SIZE_5STORED;
public const int Count = 6;
public Memory<byte> GetSlot(int index)
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual<uint>((uint)index, Count);
return Raw.Slice(index * SizeStored, SizeStored);
}
public Memory<byte> this [int index] => GetSlot(index);
public bool BattleBoxLocked
{
get => Data[0x358] != 0; // Wi-Fi/Live Tournament Active
set => Data[0x358] = value ? (byte)1 : (byte)0;
}
}

View file

@ -3,12 +3,10 @@ using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
public sealed class BattleSubway5(SAV5 sav, int offset) : SaveBlock<SAV5>(sav, offset)
public sealed class BattleSubway5(SAV5 sav, Memory<byte> raw) : SaveBlock<SAV5>(sav, raw)
{
private Span<byte> Raw => Data.AsSpan(Offset);
public int BP { get => ReadUInt16LittleEndian(Raw); set => WriteUInt16LittleEndian(Raw, (ushort)value); }
public int Flags { get => Raw[0x04]; set => Raw[0x04] = (byte)value; }
public int BP { get => ReadUInt16LittleEndian(Data); set => WriteUInt16LittleEndian(Data, (ushort)value); }
public int Flags { get => Data[0x04]; set => Data[0x04] = (byte)value; }
public bool Flag0 { get => ((Flags >> 0) & 1) != 0; set => Flags = (Flags & ~(1 << 0)) | ((value ? 1 : 0) << 0); }
public bool Flag1 { get => ((Flags >> 1) & 1) != 0; set => Flags = (Flags & ~(1 << 1)) | ((value ? 1 : 0) << 1); }
public bool Flag2 { get => ((Flags >> 2) & 1) != 0; set => Flags = (Flags & ~(1 << 2)) | ((value ? 1 : 0) << 2); }
@ -18,20 +16,20 @@ public sealed class BattleSubway5(SAV5 sav, int offset) : SaveBlock<SAV5>(sav, o
public bool SuperMulti { get => ((Flags >> 6) & 1) != 0; set => Flags = (Flags & ~(1 << 6)) | ((value ? 1 : 0) << 6); }
public bool Flag7 { get => ((Flags >> 7) & 1) != 0; set => Flags = (Flags & ~(1 << 7)) | ((value ? 1 : 0) << 7); }
public int SinglePast { get => ReadUInt16LittleEndian(Raw[0x08..]); set => WriteUInt16LittleEndian(Raw[0x08..], (ushort)value); }
public int DoublePast { get => ReadUInt16LittleEndian(Raw[0x0A..]); set => WriteUInt16LittleEndian(Raw[0x0A..], (ushort)value); }
public int MultiNPCPast { get => ReadUInt16LittleEndian(Raw[0x0C..]); set => WriteUInt16LittleEndian(Raw[0x0C..], (ushort)value); }
public int MultiFriendsPast { get => ReadUInt16LittleEndian(Raw[0x0E..]); set => WriteUInt16LittleEndian(Raw[0x0E..], (ushort)value); }
public int SuperSinglePast { get => ReadUInt16LittleEndian(Raw[0x12..]); set => WriteUInt16LittleEndian(Raw[0x12..], (ushort)value); }
public int SuperDoublePast { get => ReadUInt16LittleEndian(Raw[0x14..]); set => WriteUInt16LittleEndian(Raw[0x14..], (ushort)value); }
public int SuperMultiNPCPast { get => ReadUInt16LittleEndian(Raw[0x16..]); set => WriteUInt16LittleEndian(Raw[0x16..], (ushort)value); }
public int SuperMultiFriendsPast { get => ReadUInt16LittleEndian(Raw[0x18..]); set => WriteUInt16LittleEndian(Raw[0x18..], (ushort)value); }
public int SingleRecord { get => ReadUInt16LittleEndian(Raw[0x1A..]); set => WriteUInt16LittleEndian(Raw[0x1A..], (ushort)value); }
public int DoubleRecord { get => ReadUInt16LittleEndian(Raw[0x1C..]); set => WriteUInt16LittleEndian(Raw[0x1C..], (ushort)value); }
public int MultiNPCRecord { get => ReadUInt16LittleEndian(Raw[0x1E..]); set => WriteUInt16LittleEndian(Raw[0x1E..], (ushort)value); }
public int MultiFriendsRecord { get => ReadUInt16LittleEndian(Raw[0x20..]); set => WriteUInt16LittleEndian(Raw[0x20..], (ushort)value); }
public int SuperSingleRecord { get => ReadUInt16LittleEndian(Raw[0x24..]); set => WriteUInt16LittleEndian(Raw[0x24..], (ushort)value); }
public int SuperDoubleRecord { get => ReadUInt16LittleEndian(Raw[0x26..]); set => WriteUInt16LittleEndian(Raw[0x26..], (ushort)value); }
public int SuperMultiNPCRecord { get => ReadUInt16LittleEndian(Raw[0x28..]); set => WriteUInt16LittleEndian(Raw[0x28..], (ushort)value); }
public int SuperMultiFriendsRecord { get => ReadUInt16LittleEndian(Raw[0x2A..]); set => WriteUInt16LittleEndian(Raw[0x2A..], (ushort)value); }
public int SinglePast { get => ReadUInt16LittleEndian(Data[0x08..]); set => WriteUInt16LittleEndian(Data[0x08..], (ushort)value); }
public int DoublePast { get => ReadUInt16LittleEndian(Data[0x0A..]); set => WriteUInt16LittleEndian(Data[0x0A..], (ushort)value); }
public int MultiNPCPast { get => ReadUInt16LittleEndian(Data[0x0C..]); set => WriteUInt16LittleEndian(Data[0x0C..], (ushort)value); }
public int MultiFriendsPast { get => ReadUInt16LittleEndian(Data[0x0E..]); set => WriteUInt16LittleEndian(Data[0x0E..], (ushort)value); }
public int SuperSinglePast { get => ReadUInt16LittleEndian(Data[0x12..]); set => WriteUInt16LittleEndian(Data[0x12..], (ushort)value); }
public int SuperDoublePast { get => ReadUInt16LittleEndian(Data[0x14..]); set => WriteUInt16LittleEndian(Data[0x14..], (ushort)value); }
public int SuperMultiNPCPast { get => ReadUInt16LittleEndian(Data[0x16..]); set => WriteUInt16LittleEndian(Data[0x16..], (ushort)value); }
public int SuperMultiFriendsPast { get => ReadUInt16LittleEndian(Data[0x18..]); set => WriteUInt16LittleEndian(Data[0x18..], (ushort)value); }
public int SingleRecord { get => ReadUInt16LittleEndian(Data[0x1A..]); set => WriteUInt16LittleEndian(Data[0x1A..], (ushort)value); }
public int DoubleRecord { get => ReadUInt16LittleEndian(Data[0x1C..]); set => WriteUInt16LittleEndian(Data[0x1C..], (ushort)value); }
public int MultiNPCRecord { get => ReadUInt16LittleEndian(Data[0x1E..]); set => WriteUInt16LittleEndian(Data[0x1E..], (ushort)value); }
public int MultiFriendsRecord { get => ReadUInt16LittleEndian(Data[0x20..]); set => WriteUInt16LittleEndian(Data[0x20..], (ushort)value); }
public int SuperSingleRecord { get => ReadUInt16LittleEndian(Data[0x24..]); set => WriteUInt16LittleEndian(Data[0x24..], (ushort)value); }
public int SuperDoubleRecord { get => ReadUInt16LittleEndian(Data[0x26..]); set => WriteUInt16LittleEndian(Data[0x26..], (ushort)value); }
public int SuperMultiNPCRecord { get => ReadUInt16LittleEndian(Data[0x28..]); set => WriteUInt16LittleEndian(Data[0x28..], (ushort)value); }
public int SuperMultiFriendsRecord { get => ReadUInt16LittleEndian(Data[0x2A..]); set => WriteUInt16LittleEndian(Data[0x2A..], (ushort)value); }
}

View file

@ -2,11 +2,11 @@ using System;
namespace PKHeX.Core;
public sealed class BoxLayout5(SAV5 sav, int offset) : SaveBlock<SAV5>(sav, offset)
public sealed class BoxLayout5(SAV5 sav, Memory<byte> raw) : SaveBlock<SAV5>(sav, raw)
{
public int CurrentBox { get => Data[Offset]; set => Data[Offset] = (byte)value; }
public int GetBoxNameOffset(int box) => Offset + (0x28 * box) + 4;
public int GetBoxWallpaperOffset(int box) => Offset + 0x3C4 + box;
public int CurrentBox { get => Data[0]; set => Data[0] = (byte)value; }
public int GetBoxNameOffset(int box) => (0x28 * box) + 4;
public int GetBoxWallpaperOffset(int box) => 0x3C4 + box;
public int GetBoxWallpaper(int box)
{
@ -22,7 +22,7 @@ public sealed class BoxLayout5(SAV5 sav, int offset) : SaveBlock<SAV5>(sav, offs
Data[GetBoxWallpaperOffset(box)] = (byte)value;
}
private Span<byte> GetBoxNameSpan(int box) => Data.AsSpan(GetBoxNameOffset(box), 0x14);
private Span<byte> GetBoxNameSpan(int box) => Data.Slice(GetBoxNameOffset(box), 0x14);
public string GetBoxName(int box)
{
@ -38,12 +38,12 @@ public sealed class BoxLayout5(SAV5 sav, int offset) : SaveBlock<SAV5>(sav, offs
public byte BoxesUnlocked
{
get => Data[Offset + 0x3DD];
get => Data[0x3DD];
set
{
if (value > SAV.BoxCount)
value = (byte)SAV.BoxCount;
Data[Offset + 0x3DD] = value;
Data[0x3DD] = value;
}
}

View file

@ -6,15 +6,15 @@ namespace PKHeX.Core;
/// <summary>
/// Generation 5 Chatter Recording
/// </summary>
public sealed class Chatter5(SAV5 SAV, int offset) : SaveBlock<SAV5>(SAV, offset), IChatter
public sealed class Chatter5(SAV5 SAV, Memory<byte> raw) : SaveBlock<SAV5>(SAV, raw), IChatter
{
public bool Initialized
{
get => ReadUInt32LittleEndian(Data.AsSpan(Offset)) == 1u;
set => WriteUInt32LittleEndian(Data.AsSpan(Offset), value ? 1u : 0u);
get => ReadUInt32LittleEndian(Data) == 1u;
set => WriteUInt32LittleEndian(Data, value ? 1u : 0u);
}
public Span<byte> Recording => Data.AsSpan(Offset + sizeof(uint), IChatter.SIZE_PCM);
public Span<byte> Recording => Data.Slice(sizeof(uint), IChatter.SIZE_PCM);
public int ConfusionChance => !Initialized ? 0 : (Recording[99] ^ Recording[499] ^ Recording[699]) switch
{

View file

@ -3,7 +3,7 @@ using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
public sealed class Daycare5(SAV5 sav, int offset) : SaveBlock<SAV5>(sav, offset)
public sealed class Daycare5(SAV5 sav, Memory<byte> raw) : SaveBlock<SAV5>(sav, raw), IDaycareStorage, IDaycareRandomState<ulong>, IDaycareExperience
{
// struct daycareSlot
// bool32 occupied
@ -15,30 +15,41 @@ public sealed class Daycare5(SAV5 sav, int offset) : SaveBlock<SAV5>(sav, offset
// daycareSlot[2]
// ???->end ???
public const int DaycareSeedSize = 16; // 8 bytes, B2/W2 only
public int DaycareSlotCount => 2;
public ulong? GetSeed()
private static int GetDaycareSlotOffset(int index) => SlotSize * index;
public bool IsDaycareOccupied(int index) => ReadUInt32LittleEndian(Data[GetDaycareSlotOffset(index)..]) == 1;
public void SetDaycareOccupied(int index, bool occupied) => WriteUInt32LittleEndian(Data[GetDaycareSlotOffset(index)..], occupied ? 1u : 0);
private static int GetPKMOffset(int index) => GetDaycareSlotOffset(index) + 4;
public Memory<byte> GetDaycareSlot(int index) => Raw.Slice(GetPKMOffset(index), PokeCrypto.SIZE_5PARTY);
private static int GetDaycareEXPOffset(int slot) => GetDaycareSlotOffset(slot) + 4 + PokeCrypto.SIZE_5PARTY;
public uint GetDaycareEXP(int index) => ReadUInt32LittleEndian(Data[GetDaycareEXPOffset(index)..]);
public void SetDaycareEXP(int index, uint value) => WriteUInt32LittleEndian(Data[GetDaycareEXPOffset(index)..], value);
// 0x1C8
public bool IsEggAvailable
{
if (SAV is not SAV5B2W2)
return null;
return ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x1CC));
get => (Data[0x1C8] & 1) != 0;
set => Data[0x1C8] = (byte)(value ? (Data[0x1C8] | 1) : (Data[0x1C8] & ~1));
}
public void SetSeed(ReadOnlySpan<char> value)
// 8 bytes, B2/W2 only
public ulong Seed
{
if (SAV is not SAV5B2W2)
return;
var data = Util.GetBytesFromHexString(value);
SAV.SetData(data, Offset + 0x1CC);
get
{
if (SAV is not SAV5B2W2)
return 0;
return ReadUInt64LittleEndian(Data[0x1CC..]);
}
set
{
if (SAV is not SAV5B2W2)
return;
WriteUInt64LittleEndian(Data[0x1CC..], value);
}
}
private int GetDaycareSlotOffset(int slot) => Offset + (SlotSize * slot);
public int GetPKMOffset(int slot) => GetDaycareSlotOffset(slot) + 4;
private int GetDaycareEXPOffset(int slot) => GetDaycareSlotOffset(slot) + 4 + PokeCrypto.SIZE_5PARTY;
public bool? IsOccupied(int slot) => ReadUInt32LittleEndian(Data.AsSpan(GetDaycareSlotOffset(slot))) == 1;
public void SetOccupied(int slot, bool occupied) => WriteUInt32LittleEndian(Data.AsSpan(GetDaycareSlotOffset(slot)), occupied ? 1u : 0);
public uint? GetEXP(int slot) => ReadUInt32LittleEndian(Data.AsSpan(GetDaycareEXPOffset(slot)));
public void SetEXP(int slot, uint EXP) => WriteUInt32LittleEndian(Data.AsSpan(GetDaycareEXPOffset(slot)), EXP);
}

View file

@ -3,13 +3,13 @@ using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
public abstract class Encount5(SAV5 SAV, int offset) : SaveBlock<SAV5>(SAV, offset)
public abstract class Encount5(SAV5 SAV, Memory<byte> raw) : SaveBlock<SAV5>(SAV, raw)
{
public ushort LastLocation { get => ReadUInt16LittleEndian(Data); set => WriteUInt16LittleEndian(Data, value); }
public ushort CaptureUnknown { get => ReadUInt16LittleEndian(Data.AsSpan()[0x02..]); set => WriteUInt16LittleEndian(Data.AsSpan()[0x02..], value); }
public ushort CaptureUnknown { get => ReadUInt16LittleEndian(Data[0x02..]); set => WriteUInt16LittleEndian(Data[0x02..], value); }
public Roamer5 Roamer1 => new(Data, Offset + 4);
public Roamer5 Roamer2 => new(Data, Offset + 4 + Roamer5.SIZE);
public Roamer5 Roamer1 => new(Raw.Slice(4, Roamer5.SIZE));
public Roamer5 Roamer2 => new(Raw.Slice(4 + Roamer5.SIZE, Roamer5.SIZE));
public abstract byte SwarmSeed { get; set; }
public abstract uint SwarmMaxCountModulo { get; }
@ -21,14 +21,39 @@ public abstract class Encount5(SAV5 SAV, int offset) : SaveBlock<SAV5>(SAV, offs
}
}
public sealed class Encount5BW(SAV5BW SAV, int offset) : Encount5(SAV, offset)
public sealed class Encount5BW(SAV5BW sav, Memory<byte> raw) : Encount5(sav, raw)
{
public override byte SwarmSeed { get => Data[Offset + 0x30]; set => Data[Offset + 0x30] = value; }
// 642, 641
public byte GetRoamerState(int index)
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual<uint>((uint)index, 2);
return Data[0x2E + index];
}
public byte GetRoamerState2C(int index)
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual<uint>((uint)index, 2);
return Data[0x2C + index];
}
public override byte SwarmSeed { get => Data[0x30]; set => Data[0x30] = value; }
public override uint SwarmMaxCountModulo => 17;
public void SetRoamerState(int index, byte value)
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual<uint>((uint)index, 2);
Data[0x2E + index] = value;
}
public void SetRoamerState2C(int index, byte value)
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual<uint>((uint)index, 2);
Data[0x2C + index] = value;
}
}
public sealed class Encount5B2W2(SAV5B2W2 SAV, int offset) : Encount5(SAV, offset)
public sealed class Encount5B2W2(SAV5B2W2 sav, Memory<byte> raw) : Encount5(sav, raw)
{
public override byte SwarmSeed { get => Data[Offset + 0x2C]; set => Data[Offset + 0x2C] = value; }
public override byte SwarmSeed { get => Data[0x2C]; set => Data[0x2C] = value; }
public override uint SwarmMaxCountModulo => 19;
}

View file

@ -3,47 +3,47 @@ using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
public abstract class Entralink5(SAV5 SAV, int offset) : SaveBlock<SAV5>(SAV, offset)
public abstract class Entralink5(SAV5 SAV, Memory<byte> raw) : SaveBlock<SAV5>(SAV, raw)
{
public ushort WhiteForestLevel
{
get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x0C));
set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x0C), value);
get => ReadUInt16LittleEndian(Data[0x0C..]);
set => WriteUInt16LittleEndian(Data[0x0C..], value);
}
public ushort BlackCityLevel
{
get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x0E));
set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x0E), value);
get => ReadUInt16LittleEndian(Data[0x0E..]);
set => WriteUInt16LittleEndian(Data[0x0E..], value);
}
}
public sealed class Entralink5BW(SAV5BW SAV, int offset) : Entralink5(SAV, offset)
public sealed class Entralink5BW(SAV5BW SAV, Memory<byte> raw) : Entralink5(SAV, raw)
{
public byte MissionsComplete
{
get => Data[Offset + 0x1A4];
set => Data[Offset + 0x1A4] = value;
get => Data[0x1A4];
set => Data[0x1A4] = value;
}
}
public sealed class Entralink5B2W2(SAV5B2W2 SAV, int offset) : Entralink5(SAV, offset)
public sealed class Entralink5B2W2(SAV5B2W2 SAV, Memory<byte> raw) : Entralink5(SAV, raw)
{
public byte PassPower1
{
get => Data[Offset + 0x1A0];
set => Data[Offset + 0x1A0] = value;
get => Data[0x1A0];
set => Data[0x1A0] = value;
}
public byte PassPower2
{
get => Data[Offset + 0x1A1];
set => Data[Offset + 0x1A1] = value;
get => Data[0x1A1];
set => Data[0x1A1] = value;
}
public byte PassPower3
{
get => Data[Offset + 0x1A2];
set => Data[Offset + 0x1A2] = value;
get => Data[0x1A2];
set => Data[0x1A2] = value;
}
}

View file

@ -6,41 +6,35 @@ namespace PKHeX.Core;
/// <summary>
/// Generation 5 Entrée Forest
/// </summary>
public sealed class EntreeForest
public sealed class EntreeForest(SAV5 sav, Memory<byte> raw) : SaveBlock<SAV5>(sav, raw)
{
/// <summary>
/// Areas 1 through 8 have 20 slots.
/// </summary>
/// <summary> Areas 1 through 8 have 20 slots. </summary>
private const byte Count18 = 20;
/// <summary>
/// 9th Area has only 10 slots.
/// </summary>
/// <summary> 9th Area has only 10 slots. </summary>
private const byte Count9 = 10;
private const int TotalSlots = Count18 + (3 * 8 * Count18) + (3 * Count9); // 530
/// <summary>
/// Areas 3 through 8 can be unlocked (set a value 0 to 6).
/// </summary>
/// <summary> Areas 3 through 8 can be unlocked (set a value 0 to 6). </summary>
private const byte MaxUnlock38Areas = 6;
private const int EncryptionSeedOffset = SIZE - 4; // 0x84C
public const int SIZE = 0x850;
private readonly byte[] Data;
private Span<byte> DataRegion => Data[..EncryptionSeedOffset]; // 0x84C
public EntreeForest(byte[] data) => CryptRegion(Data = data);
public byte[] Write()
private bool IsDecrypted;
public void EndAccess() => EnsureDecrypted(false);
public void StartAccess() => EnsureDecrypted();
public void EnsureDecrypted(bool state = true)
{
byte[] data = (byte[])Data.Clone();
CryptRegion(data);
return data;
if (IsDecrypted == state)
return;
PokeCrypto.CryptArray(DataRegion, EncryptionSeed);
IsDecrypted = state;
}
private void CryptRegion(Span<byte> data) => PokeCrypto.CryptArray(data[..EncryptionSeedOffset], EncryptionSeed);
/// <summary>
/// Gets all Entree Slot data.
/// </summary>
@ -48,9 +42,10 @@ public sealed class EntreeForest
{
get
{
EnsureDecrypted();
var slots = new EntreeSlot[TotalSlots];
for (int i = 0; i < slots.Length; i++)
slots[i] = new EntreeSlot(Data.AsMemory(i * EntreeSlot.SIZE, EntreeSlot.SIZE)) { Area = GetSlotArea(i) };
slots[i] = new EntreeSlot(Raw.Slice(i * EntreeSlot.SIZE, EntreeSlot.SIZE)) { Area = GetSlotArea(i) };
return slots;
}
}
@ -75,8 +70,8 @@ public sealed class EntreeForest
public uint EncryptionSeed
{
get => ReadUInt32LittleEndian(Data.AsSpan(EncryptionSeedOffset));
private set => WriteUInt32LittleEndian(Data.AsSpan(EncryptionSeedOffset), value);
get => ReadUInt32LittleEndian(Data[EncryptionSeedOffset..]);
private set => WriteUInt32LittleEndian(Data[EncryptionSeedOffset..], value);
}
public void UnlockAllAreas()

View file

@ -0,0 +1,52 @@
using System;
using System.Buffers.Binary;
namespace PKHeX.Core;
public abstract class EventWork5(SAV5 sav, Memory<byte> raw) : SaveBlock<SAV5>(sav, raw), IEventFlag37
{
protected abstract Span<byte> WorkSpan { get; }
protected abstract Span<byte> FlagSpan { get; }
public bool GetEventFlag(int flagNumber) => FlagUtil.GetFlag(FlagSpan, flagNumber);
public void SetEventFlag(int flagNumber, bool value) => FlagUtil.SetFlag(FlagSpan, flagNumber, value);
public ushort GetWork(int index) => BinaryPrimitives.ReadUInt16LittleEndian(WorkSpan[(index * sizeof(ushort))..]);
public void SetWork(int index, ushort value) => BinaryPrimitives.WriteUInt16LittleEndian(WorkSpan[(index * sizeof(ushort))..], value);
public abstract int EventWorkCount { get; }
public abstract int EventFlagCount { get; }
}
public sealed class EventWork5BW(SAV5BW sav, Memory<byte> raw) : EventWork5(sav, raw)
{
public const int OffsetEventWork = 0;
public const int OffsetEventFlag = OffsetEventWork + (CountEventFlag * sizeof(ushort));
public const int CountEventWork = 0x13E;
public const int CountEventFlag = 0xB60;
protected override Span<byte> WorkSpan => Data[..(CountEventWork * sizeof(ushort))];
protected override Span<byte> FlagSpan => Data.Slice(OffsetEventFlag, CountEventFlag / 8);
public override int EventWorkCount => CountEventWork;
public override int EventFlagCount => CountEventFlag;
private const int WorkRoamer = 192;
public ushort GetWorkRoamer() => GetWork(WorkRoamer);
public void SetWorkRoamer(ushort value) => SetWork(WorkRoamer, value);
}
public sealed class EventWork5B2W2(SAV5B2W2 sav, Memory<byte> raw) : EventWork5(sav, raw)
{
public const int OffsetEventWork = 0;
public const int OffsetEventFlag = OffsetEventWork + (CountEventFlag * sizeof(ushort));
public const int CountEventWork = 0x1AF;
public const int CountEventFlag = 0xBF8;
protected override Span<byte> WorkSpan => Data[..(CountEventWork * sizeof(ushort))];
protected override Span<byte> FlagSpan => Data.Slice(OffsetEventFlag, CountEventFlag / 8);
public override int EventWorkCount => CountEventWork;
public override int EventFlagCount => CountEventFlag;
}

View file

@ -3,7 +3,7 @@ using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
public sealed class FestaBlock5(SAV5B2W2 SAV, int offset) : SaveBlock<SAV5B2W2>(SAV, offset)
public sealed class FestaBlock5(SAV5B2W2 SAV, Memory<byte> raw) : SaveBlock<SAV5B2W2>(SAV, raw)
{
public const ushort MaxScore = 9999;
public const int FunfestFlag = 2438;
@ -12,44 +12,44 @@ public sealed class FestaBlock5(SAV5B2W2 SAV, int offset) : SaveBlock<SAV5B2W2>(
public ushort Hosted
{
get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0xF0));
set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0xF0), Math.Min(MaxScore, value));
get => ReadUInt16LittleEndian(Data[0xF0..]);
set => WriteUInt16LittleEndian(Data[0xF0..], Math.Min(MaxScore, value));
}
public ushort Participated
{
get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0xF2));
set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0xF2), Math.Min(MaxScore, value));
get => ReadUInt16LittleEndian(Data[0xF2..]);
set => WriteUInt16LittleEndian(Data[0xF2..], Math.Min(MaxScore, value));
}
public ushort Completed
{
get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0xF4));
set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0xF4), Math.Min(MaxScore, value));
get => ReadUInt16LittleEndian(Data[0xF4..]);
set => WriteUInt16LittleEndian(Data[0xF4..], Math.Min(MaxScore, value));
}
public ushort TopScores
{
get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0xF6));
set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0xF6), Math.Min(MaxScore, value));
get => ReadUInt16LittleEndian(Data[0xF6..]);
set => WriteUInt16LittleEndian(Data[0xF6..], Math.Min(MaxScore, value));
}
public byte WhiteEXP
{
get => Data[Offset + 0xF8];
set => Data[Offset + 0xF8] = value;
get => Data[0xF8];
set => Data[0xF8] = value;
}
public byte BlackEXP
{
get => Data[Offset + 0xF9];
set => Data[Offset + 0xF9] = value;
get => Data[0xF9];
set => Data[0xF9] = value;
}
public byte Participants
{
get => Data[Offset + 0xFA];
set => Data[Offset + 0xFA] = value;
get => Data[0xFA];
set => Data[0xFA] = value;
}
private static int GetMissionRecordOffset(int mission)
@ -61,20 +61,20 @@ public sealed class FestaBlock5(SAV5B2W2 SAV, int offset) : SaveBlock<SAV5B2W2>(
public Funfest5Score GetMissionRecord(int mission)
{
var raw = ReadUInt32LittleEndian(Data.AsSpan(Offset + GetMissionRecordOffset(mission)));
var raw = ReadUInt32LittleEndian(Data[GetMissionRecordOffset(mission)..]);
return new Funfest5Score(raw);
}
public void SetMissionRecord(int mission, Funfest5Score score)
{
var value = score.RawValue;
WriteUInt32LittleEndian(Data.AsSpan(Offset + GetMissionRecordOffset(mission)), value);
WriteUInt32LittleEndian(Data[GetMissionRecordOffset(mission)..], value);
}
public bool IsFunfestMissionsUnlocked
{
get => SAV.GetEventFlag(FunfestFlag);
set => SAV.SetEventFlag(FunfestFlag, value);
get => SAV.EventWork.GetEventFlag(FunfestFlag);
set => SAV.EventWork.SetEventFlag(FunfestFlag, value);
}
public bool IsFunfestMissionUnlocked(int mission)
@ -88,7 +88,7 @@ public sealed class FestaBlock5(SAV5B2W2 SAV, int offset) : SaveBlock<SAV5B2W2>(
var req = FunfestMissionUnlockFlagRequired[mission];
foreach (var f in req)
{
if (!SAV.GetEventFlag(f))
if (!SAV.EventWork.GetEventFlag(f))
return false;
}
return true;
@ -102,7 +102,7 @@ public sealed class FestaBlock5(SAV5B2W2 SAV, int offset) : SaveBlock<SAV5B2W2>(
IsFunfestMissionsUnlocked = true;
var req = FunfestMissionUnlockFlagRequired[mission];
foreach (var f in req)
SAV.SetEventFlag(f, true);
SAV.EventWork.SetEventFlag(f, true);
}
public void UnlockAllFunfestMissions()

View file

@ -2,18 +2,11 @@ using System;
namespace PKHeX.Core;
public sealed class MedalList5 : SaveBlock<SAV5B2W2>
public sealed class MedalList5(SAV5B2W2 SAV, Memory<byte> raw) : SaveBlock<SAV5B2W2>(SAV, raw)
{
private const int MAX_MEDALS = 255;
private readonly Medal5[] Medals;
public MedalList5(SAV5B2W2 SAV, int offset) : base(SAV, offset)
{
var memory = Data.AsMemory(Offset, MAX_MEDALS * Medal5.SIZE);
Medals = GetMedals(memory);
}
private static Medal5[] GetMedals(Memory<byte> memory)
public static Medal5[] GetMedals(Memory<byte> memory)
{
var count = memory.Length / Medal5.SIZE;
var result = new Medal5[count];
@ -29,18 +22,11 @@ public sealed class MedalList5 : SaveBlock<SAV5B2W2>
return new Medal5(memory.Slice(index * Medal5.SIZE, Medal5.SIZE));
}
public Medal5 this[int index]
{
get => Medals[index];
set => Medals[index] = value;
}
public Medal5 this[int index] => GetMedal(Raw, index);
public void ObtainAll(DateOnly date, bool unread = true)
{
foreach (var medal in Medals)
{
if (!medal.IsObtained)
medal.Obtain(date, unread);
}
for (int i = 0; i < MAX_MEDALS; i++)
this[i].Obtain(date, unread);
}
}

View file

@ -3,24 +3,24 @@ using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
public abstract class Misc5(SAV5 sav, int offset) : SaveBlock<SAV5>(sav, offset), IGymTeamInfo
public abstract class Misc5(SAV5 sav, Memory<byte> raw) : SaveBlock<SAV5>(sav, raw), IGymTeamInfo
{
public uint Money
{
get => ReadUInt32LittleEndian(Data.AsSpan(Offset));
set => WriteUInt32LittleEndian(Data.AsSpan(Offset), value);
get => ReadUInt32LittleEndian(Data);
set => WriteUInt32LittleEndian(Data, value);
}
public int Badges
{
get => Data[Offset + 0x4];
set => Data[Offset + 0x4] = (byte)value;
get => Data[0x4];
set => Data[0x4] = (byte)value;
}
public ushort PokeTransferMinigameScore
{
get => ReadUInt16LittleEndian(Data.AsSpan(Offset + TransferMinigameScoreOffset));
set => WriteUInt16LittleEndian(Data.AsSpan(Offset + TransferMinigameScoreOffset), value);
get => ReadUInt16LittleEndian(Data[TransferMinigameScoreOffset..]);
set => WriteUInt16LittleEndian(Data[TransferMinigameScoreOffset..], value);
}
protected abstract int BadgeVictoryOffset { get; }
@ -31,29 +31,29 @@ public abstract class Misc5(SAV5 sav, int offset) : SaveBlock<SAV5>(sav, offset)
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual<uint>(badge, 8);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual<uint>(slot, 6);
return Offset + BadgeVictoryOffset + (int)(((6 * badge) + slot) * sizeof(ushort));
return BadgeVictoryOffset + (int)(((6 * badge) + slot) * sizeof(ushort));
}
public ushort GetBadgeVictorySpecies(uint badge, uint slot)
{
var ofs = GetBadgeVictorySpeciesOffset(badge, slot);
return ReadUInt16LittleEndian(Data.AsSpan(ofs));
return ReadUInt16LittleEndian(Data[ofs..]);
}
public void SetBadgeVictorySpecies(uint badge, uint slot, ushort species)
{
var ofs = GetBadgeVictorySpeciesOffset(badge, slot);
WriteUInt16LittleEndian(SAV.Data.AsSpan(ofs), species);
WriteUInt16LittleEndian(Data[ofs..], species);
}
}
public sealed class Misc5BW(SAV5BW sav, int offset) : Misc5(sav, offset)
public sealed class Misc5BW(SAV5BW sav, Memory<byte> raw) : Misc5(sav, raw)
{
protected override int TransferMinigameScoreOffset => 0x14;
protected override int BadgeVictoryOffset => 0x58; // thru 0xB7
}
public sealed class Misc5B2W2(SAV5B2W2 sav, int offset) : Misc5(sav, offset)
public sealed class Misc5B2W2(SAV5B2W2 sav, Memory<byte> raw) : Misc5(sav, raw)
{
protected override int TransferMinigameScoreOffset => 0x18;
protected override int BadgeVictoryOffset => 0x5C; // thru 0xBB

View file

@ -1,29 +1,28 @@
using System;
namespace PKHeX.Core;
public sealed class Musical5(SAV5 SAV, int offset) : SaveBlock<SAV5>(SAV, offset)
public sealed class Musical5(SAV5 SAV, Memory<byte> raw) : SaveBlock<SAV5>(SAV, raw)
{
private const int PropOffset = 0x258;
public void UnlockAllMusicalProps()
{
// 100 props, which is 12.5 bytes of bitflags.
var bitFieldOffset = Offset + PropOffset;
for (int i = 0; i < 0xC; i++)
Data[bitFieldOffset + i] = 0xFF;
Data[bitFieldOffset + 0xC] = 0x0F; // top 4 bits unset, to complete multiple of 8 (100=>104 bits).
Data[PropOffset + i] = 0xFF;
Data[PropOffset + 0xC] = 0x0F; // top 4 bits unset, to complete multiple of 8 (100=>104 bits).
}
public bool GetHasProp(int prop)
{
var bitFieldOffset = Offset + PropOffset;
var bitOffset = prop >> 3;
return SAV.GetFlag(bitFieldOffset + bitOffset, prop & 7);
return SAV.GetFlag(Data, PropOffset + bitOffset, prop & 7);
}
public void SetHasProp(int prop, bool value = true)
{
var bitFieldOffset = Offset + PropOffset;
var bitOffset = prop >> 3;
SAV.SetFlag(bitFieldOffset + bitOffset, prop & 7, value);
SAV.SetFlag(Data, PropOffset + bitOffset, prop & 7, value);
}
}

View file

@ -1,8 +1,9 @@
using System;
using System.Collections.Generic;
namespace PKHeX.Core;
public sealed class MyItem5B2W2(SAV5B2W2 SAV, int offset) : MyItem(SAV, offset)
public sealed class MyItem5B2W2(SAV5B2W2 SAV, Memory<byte> raw) : MyItem(SAV, raw)
{
// offsets/pouch sizes are the same for both B/W and B2/W2, but Key Item permissions are different
private const int HeldItem = 0x000; // 0
@ -18,11 +19,11 @@ public sealed class MyItem5B2W2(SAV5B2W2 SAV, int offset) : MyItem(SAV, offset)
var info = ItemStorage5B2W2.Instance;
InventoryPouch4[] pouch =
[
new(InventoryType.Items, info, 999, Offset + HeldItem),
new(InventoryType.KeyItems, info, 1, Offset + KeyItem),
new(InventoryType.TMHMs, info, 1, Offset + TMHM),
new(InventoryType.Medicine, info, 999, Offset + Medicine),
new(InventoryType.Berries, info, 999, Offset + Berry),
new(InventoryType.Items, info, 999, HeldItem),
new(InventoryType.KeyItems, info, 1, KeyItem),
new(InventoryType.TMHMs, info, 1, TMHM),
new(InventoryType.Medicine, info, 999, Medicine),
new(InventoryType.Berries, info, 999, Berry),
];
return pouch.LoadAll(Data);
}

View file

@ -1,8 +1,9 @@
using System;
using System.Collections.Generic;
namespace PKHeX.Core;
public sealed class MyItem5BW(SAV5BW SAV, int offset) : MyItem(SAV, offset)
public sealed class MyItem5BW(SAV5BW SAV, Memory<byte> raw) : MyItem(SAV, raw)
{
// offsets/pouch sizes are the same for both B/W and B2/W2, but Key Item permissions are different
private const int HeldItem = 0x000; // 0
@ -18,11 +19,11 @@ public sealed class MyItem5BW(SAV5BW SAV, int offset) : MyItem(SAV, offset)
var info = ItemStorage5BW.Instance;
InventoryPouch4[] pouch =
[
new(InventoryType.Items, info, 999, Offset + HeldItem),
new(InventoryType.KeyItems, info, 1, Offset + KeyItem),
new(InventoryType.TMHMs, info, 1, Offset + TMHM),
new(InventoryType.Medicine, info, 999, Offset + Medicine),
new(InventoryType.Berries, info, 999, Offset + Berry),
new(InventoryType.Items, info, 999, HeldItem),
new(InventoryType.KeyItems, info, 1, KeyItem),
new(InventoryType.TMHMs, info, 1, TMHM),
new(InventoryType.Medicine, info, 999, Medicine),
new(InventoryType.Berries, info, 999, Berry),
];
return pouch.LoadAll(Data);
}

View file

@ -3,7 +3,7 @@ using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
public sealed class MysteryBlock5(SAV5 sav, int offset) : SaveBlock<SAV5>(sav, offset)
public sealed class MysteryBlock5(SAV5 sav, Memory<byte> raw) : SaveBlock<SAV5>(sav, raw), IMysteryGiftStorage, IMysteryGiftFlags
{
private const int FlagStart = 0;
private const int MaxReceivedFlag = 2048;
@ -12,65 +12,95 @@ public sealed class MysteryBlock5(SAV5 sav, int offset) : SaveBlock<SAV5>(sav, o
private const int CardStart = FlagStart + FlagRegionSize;
private const int DataSize = 0xA90;
private int SeedOffset => Offset + DataSize;
// Everything is stored encrypted, and only decrypted on demand. Only crypt on object fetch...
public EncryptedMysteryGiftAlbum GiftAlbum
private uint AlbumSeed
{
get
{
uint seed = ReadUInt32LittleEndian(Data.AsSpan(SeedOffset));
byte[] wcData = SAV.Data.AsSpan(Offset + FlagStart, 0xA90).ToArray(); // Encrypted, Decrypt
return GetAlbum(seed, wcData);
}
set
{
var wcData = SetAlbum(value);
// Write Back
wcData.CopyTo(Data, Offset + FlagStart);
WriteUInt32LittleEndian(Data.AsSpan(SeedOffset), value.Seed);
}
get => ReadUInt32LittleEndian(Data[DataSize..]);
set => WriteUInt32LittleEndian(Data[DataSize..], value);
}
private static EncryptedMysteryGiftAlbum GetAlbum(uint seed, byte[] wcData)
private Span<byte> DataRegion => Data[..^4]; // 0xA90
private Span<byte> FlagRegion => Data[..CardStart]; // 0x100
private bool IsDecrypted;
public void EndAccess() => EnsureDecrypted(false);
private void EnsureDecrypted(bool state = true)
{
PokeCrypto.CryptArray(wcData, seed);
var flags = new bool[MaxReceivedFlag];
var gifts = new DataMysteryGift[MaxCardsPresent];
var Info = new EncryptedMysteryGiftAlbum(gifts, flags, seed);
// 0x100 Bytes for Used Flags
for (int i = 0; i < Info.Flags.Length; i++)
Info.Flags[i] = ((wcData[i / 8] >> (i % 8)) & 0x1) == 1;
// 12 PGFs
for (int i = 0; i < Info.Gifts.Length; i++)
{
var data = wcData.AsSpan(CardStart + (i * PGF.Size), PGF.Size).ToArray();
Info.Gifts[i] = new PGF(data);
}
return Info;
if (IsDecrypted == state)
return;
PokeCrypto.CryptArray(DataRegion, AlbumSeed);
IsDecrypted = state;
}
private static byte[] SetAlbum(EncryptedMysteryGiftAlbum value)
public void ClearReceivedFlags()
{
byte[] wcData = new byte[0xA90];
// Toss back into byte[]
for (int i = 0; i < value.Flags.Length; i++)
{
if (value.Flags[i])
wcData[i / 8] |= (byte) (1 << (i & 7));
}
var span = wcData.AsSpan(CardStart);
for (int i = 0; i < value.Gifts.Length; i++)
value.Gifts[i].Data.CopyTo(span[(i * PGF.Size)..]);
// Decrypted, Encrypt
PokeCrypto.CryptArray(wcData, value.Seed);
return wcData;
EnsureDecrypted();
FlagRegion.Clear();
}
private static int GetGiftOffset(int index)
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, (uint)MaxCardsPresent);
return CardStart + (index * PGF.Size);
}
private Span<byte> GetCardSpan(int index)
{
var offset = GetGiftOffset(index);
EnsureDecrypted();
return Data.Slice(offset, PGF.Size);
}
public PGF GetMysteryGift(int index) => new(GetCardSpan(index).ToArray());
public void SetMysteryGift(int index, PGF pgf)
{
if ((uint)index > MaxCardsPresent)
throw new ArgumentOutOfRangeException(nameof(index));
if (pgf.Data.Length != PGF.Size)
throw new InvalidCastException(nameof(pgf));
SAV.SetData(GetCardSpan(index), pgf.Data);
}
public int MysteryGiftReceivedFlagMax => MaxReceivedFlag;
public bool GetMysteryGiftReceivedFlag(int index)
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, (uint)MaxReceivedFlag);
EnsureDecrypted();
return FlagUtil.GetFlag(Data, index); // offset 0
}
public void SetMysteryGiftReceivedFlag(int index, bool value)
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, (uint)MaxReceivedFlag);
EnsureDecrypted();
FlagUtil.SetFlag(Data, index, value); // offset 0
}
public int GiftCountMax => MaxCardsPresent;
DataMysteryGift IMysteryGiftStorage.GetMysteryGift(int index) => GetMysteryGift(index);
void IMysteryGiftStorage.SetMysteryGift(int index, DataMysteryGift gift) => SetMysteryGift(index, (PGF)gift);
}
public sealed class GTS5(SAV5 sav, Memory<byte> raw) : SaveBlock<SAV5>(sav, raw)
{
// 0x08: Stored Upload
private const int SizeStored = PokeCrypto.SIZE_5STORED;
public Memory<byte> Upload => Raw[..SizeStored];
}
public sealed class GlobalLink5(SAV5 sav, Memory<byte> raw) : SaveBlock<SAV5>(sav, raw)
{
// 0x08: Stored Upload
private const int SizeStored = PokeCrypto.SIZE_5STORED;
public Memory<byte> Upload => Raw.Slice(8, SizeStored);
}
public sealed class AdventureInfo5(SAV5 sav, Memory<byte> raw) : SaveBlock<SAV5>(sav, raw)
{
public uint SecondsToStart { get => ReadUInt32LittleEndian(Data[0x34..]); set => WriteUInt32LittleEndian(Data[0x34..], value); }
public uint SecondsToFame { get => ReadUInt32LittleEndian(Data[0x3C..]); set => WriteUInt32LittleEndian(Data[0x3C..], value); }
}

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