mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-25 21:40:21 +00:00
Refactoring: Rework saveblock to be Memory<byte> based (#4200)
This commit is contained in:
parent
2b63c4b013
commit
fa80dac2ac
311 changed files with 5669 additions and 4525 deletions
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
211
PKHeX.Core/Saves/Encryption/ColoCrypto.cs
Normal file
211
PKHeX.Core/Saves/Encryption/ColoCrypto.cs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)); }
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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}";
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
public interface IDaycareEggState
|
||||
{
|
||||
bool IsEggAvailable { get; set; }
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
public interface IDaycareExperience
|
||||
{
|
||||
uint GetDaycareEXP(int index);
|
||||
void SetDaycareEXP(int index, uint value);
|
||||
}
|
8
PKHeX.Core/Saves/Substructures/Daycare/IDaycareMulti.cs
Normal file
8
PKHeX.Core/Saves/Substructures/Daycare/IDaycareMulti.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
public interface IDaycareMulti
|
||||
{
|
||||
int DaycareCount { get; }
|
||||
|
||||
IDaycareStorage this[int index] { get; }
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
public interface IDaycareRandomState<T>
|
||||
{
|
||||
T Seed { get; set; }
|
||||
}
|
16
PKHeX.Core/Saves/Substructures/Daycare/IDaycareStorage.cs
Normal file
16
PKHeX.Core/Saves/Substructures/Daycare/IDaycareStorage.cs
Normal 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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
23
PKHeX.Core/Saves/Substructures/Gen5/BattleBox5.cs
Normal file
23
PKHeX.Core/Saves/Substructures/Gen5/BattleBox5.cs
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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); }
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
52
PKHeX.Core/Saves/Substructures/Gen5/EventWork5.cs
Normal file
52
PKHeX.Core/Saves/Substructures/Gen5/EventWork5.cs
Normal 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;
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue