Skip to content

Commit

Permalink
Split gen5-7 saves with inheritance (#2319)
Browse files Browse the repository at this point in the history
refer to pull request comments for summary
  • Loading branch information
kwsch committed Jun 9, 2019
1 parent 84d1354 commit 1b02819
Show file tree
Hide file tree
Showing 93 changed files with 4,266 additions and 3,460 deletions.
5 changes: 3 additions & 2 deletions PKHeX.Core/Editing/Saves/Slots/Extensions.cs
Expand Up @@ -15,6 +15,7 @@ public static void SetPKM(this SaveFile sav, StorageSlotOffset slot, PKM pkm)
else
sav.SetStoredSlot(pkm, slot.Offset);
}

public static IReadOnlyList<PKM> GetAllPKM(this SaveFile sav)
{
var pkms = new List<PKM>();
Expand Down Expand Up @@ -139,8 +140,8 @@ private static List<StorageSlotOffset> GetExtraSlots7(SAV7 sav, bool all)
if (!all)
return list;

for (int i = 0; i < SAV7.ResortCount; i++)
list.Add(new StorageSlotOffset { Type = StorageSlotType.Resort, Offset = sav.GetResortSlotOffset(i) });
for (int i = 0; i < ResortSave7.ResortCount; i++)
list.Add(new StorageSlotOffset { Type = StorageSlotType.Resort, Offset = sav.ResortSave.GetResortSlotOffset(i) });
return list;
}
}
Expand Down
8 changes: 2 additions & 6 deletions PKHeX.Core/MysteryGifts/MysteryUtil.cs
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;

using static PKHeX.Core.MessageStrings;
Expand Down Expand Up @@ -113,14 +112,11 @@ public static bool IsCardCompatible(this MysteryGift g, SaveFile SAV, out string

if (g is WC6 && g.CardID == 2048 && g.ItemID == 726) // Eon Ticket (OR/AS)
{
if (!SAV.ORAS)
if (!(SAV is SAV6AO))
{
message = MsgMysteryGiftSlotSpecialReject;
return false;
}

// Set the special recieved data
BitConverter.GetBytes(WC6.EonTicketConst).CopyTo(SAV.Data, ((SAV6)SAV).EonTicket);
}

message = null;
Expand Down
120 changes: 62 additions & 58 deletions PKHeX.Core/Saves/Blocks/BlockInfo3DS.cs
Expand Up @@ -3,17 +3,35 @@
namespace PKHeX.Core
{
/// <summary>
/// Gen6+ Block Info
/// Gen6+ Block Info (inside BEEF chunk)
/// </summary>
public sealed class BlockInfo3DS : BlockInfo
public abstract class BlockInfo3DS : BlockInfo
{
public ushort Checksum;
private int BlockInfoOffset;
private readonly Func<byte[], int, int, ushort> CheckFunc;
public BlockInfo3DS() { }
private BlockInfo3DS(Func<byte[], int, int, ushort> func) => CheckFunc = func;
private int ChecksumOffset => BlockInfoOffset + 6 + ((int)ID * 8);
private ushort GetChecksum(byte[] data) => CheckFunc(data, Offset, Length);
private readonly int BlockInfoOffset;

// ==chunk def== @ BlockInfoOffset
// u64 timestamp1
// u64 timestamp2
// u8[4] BEEF magic
// n*{blockInfo}, where n varies per sav type

// ==block info def==
// u32 length
// u16 id
// u16 checksum

// when stored, each block size is rounded up to nearest 0x200, and the next chunk is immediately after

protected BlockInfo3DS(int bo, uint id, int ofs, int len)
{
BlockInfoOffset = bo;
ID = id;
Offset = ofs;
Length = len;
}

private int ChecksumOffset => BlockInfoOffset + 0x14 + ((int)ID * 8) + 6;
protected abstract ushort GetChecksum(byte[] data);

protected override bool ChecksumValid(byte[] data)
{
Expand All @@ -27,68 +45,54 @@ protected override void SetChecksum(byte[] data)
ushort chk = GetChecksum(data);
BitConverter.GetBytes(chk).CopyTo(data, ChecksumOffset);
}
}

/// <summary>
/// Gets the <see cref="BlockInfo"/> table for the input <see cref="data"/>.
/// </summary>
/// <param name="data">Complete data array</param>
/// <param name="blockInfoOffset">Offset the <see cref="BlockInfo"/> starts at.</param>
/// <param name="CheckFunc">Checksum method for validating each block.</param>
public static BlockInfo[] GetBlockInfoData(byte[] data, out int blockInfoOffset, Func<byte[], int, int, ushort> CheckFunc)
{
blockInfoOffset = data.Length - 0x200 + 0x10;
if (BitConverter.ToUInt32(data, blockInfoOffset) != SaveUtil.BEEF)
blockInfoOffset -= 0x200; // No savegames have more than 0x3D blocks, maybe in the future?
int len = data.Length;
return GetBlockInfo(data, ref blockInfoOffset, CheckFunc, len);
}
public sealed class BlockInfo6 : BlockInfo3DS
{
public BlockInfo6(int bo, uint id, int ofs, int len) : base(bo, id, ofs, len) { }
protected override ushort GetChecksum(byte[] data) => Checksums.CRC16_CCITT(data, Offset, Length);
}

public sealed class BlockInfo7 : BlockInfo3DS
{
public BlockInfo7(int bo, uint id, int ofs, int len) : base(bo, id, ofs, len) { }
protected override ushort GetChecksum(byte[] data) => Checksums.CRC16(data, Offset, Length);
}

public sealed class BlockInfo7b : BlockInfo3DS
{
public BlockInfo7b(int bo, uint id, int ofs, int len) : base(bo, id, ofs, len) { }
protected override ushort GetChecksum(byte[] data) => Checksums.CRC16NoInvert(data, Offset, Length);
}

public static class BlockInfoBEEFUtil
{
/// <summary>
/// Gets the <see cref="BlockInfo"/> table for the input <see cref="data"/>.
/// Gets the <see cref="BlockInfo"/> for the input <see cref="data"/>.
/// </summary>
/// <param name="data">Complete data array</param>
/// <param name="blockInfoOffset">Offset the <see cref="BlockInfo"/> starts at.</param>
/// <param name="CheckFunc">Checksum method for validating each block.</param>
/// <param name="dataLength"></param>
public static BlockInfo[] GetBlockInfoData(byte[] data, ref int blockInfoOffset, Func<byte[], int, int, ushort> CheckFunc, int dataLength)
{
if (BitConverter.ToUInt32(data, blockInfoOffset) != SaveUtil.BEEF)
blockInfoOffset -= 0x200; // No savegames have more than 0x3D blocks, maybe in the future?
return GetBlockInfo(data, ref blockInfoOffset, CheckFunc, dataLength);
}

private static BlockInfo[] GetBlockInfo(byte[] data, ref int blockInfoOffset, Func<byte[], int, int, ushort> CheckFunc, int dataLength)
public static void DumpComputedBlockInfo(byte[] data, int blockInfoOffset)
{
int count = (dataLength - blockInfoOffset - 0x8) / 8;
blockInfoOffset += 4;
blockInfoOffset += 0x14;

var Blocks = new BlockInfo[count];
int CurrentPosition = 0;
for (int i = 0; i < Blocks.Length; i++)
uint CurrentPosition = 0;
for (int i = 0;; i++)
{
int ofs = i * 8;
Blocks[i] = new BlockInfo3DS(CheckFunc)
{
Offset = CurrentPosition,
Length = BitConverter.ToInt32(data, blockInfoOffset + ofs + 0),
ID = BitConverter.ToUInt16(data, blockInfoOffset + ofs + 4),
Checksum = BitConverter.ToUInt16(data, blockInfoOffset + ofs + 6),
int ofs = blockInfoOffset + (i * 8);

BlockInfoOffset = blockInfoOffset
};
var Offset = CurrentPosition;
var Length = BitConverter.ToUInt32(data, ofs + 0);
if (Length == 0)
break;
var ID = BitConverter.ToUInt16(data, ofs + 4);
// var Checksum = BitConverter.ToUInt16(data, ofs + 6);
Console.WriteLine($"ID={ID}, Offset=0x{Offset:X5}, Length=0x{Length:X5}");

// Expand out to nearest 0x200
var remainder = Blocks[i].Length & 0x1FF;
CurrentPosition += remainder == 0 ? Blocks[i].Length : Blocks[i].Length + 0x200 - remainder;

if (Blocks[i].ID != 0 || i == 0)
continue;
count = i;
break;
var remainder = Length & 0x1FF;
CurrentPosition += remainder == 0 ? Length : Length + 0x200 - remainder;
}
// Fix Final Array Lengths
Array.Resize(ref Blocks, count);
return Blocks;
}
}
}
28 changes: 16 additions & 12 deletions PKHeX.Core/Saves/SAV1.cs
Expand Up @@ -18,23 +18,30 @@ public sealed class SAV1 : SaveFile
return 1 <= gen && gen <= 2;
}).ToArray();

public SAV1(byte[] data = null, GameVersion versionOverride = GameVersion.Any)
public SAV1(GameVersion versionOverride = GameVersion.RBY, bool japanese = false) : base(SaveUtil.SIZE_G1RAW)
{
Data = data ?? new byte[SaveUtil.SIZE_G1RAW];
BAK = (byte[])Data.Clone();
Exportable = !IsRangeEmpty(0, Data.Length);
Version = versionOverride;
Japanese = japanese;
Offsets = Japanese ? SAV1Offsets.JPN : SAV1Offsets.INT;

Initialize(versionOverride);
ClearBoxes();
}

if (versionOverride != GameVersion.Any)
Version = versionOverride;
else if(data == null)
Version = GameVersion.RBY;
else Version = SaveUtil.GetIsG1SAV(Data);
public SAV1(byte[] data, GameVersion versionOverride = GameVersion.Any) : base(data)
{
Version = versionOverride != GameVersion.Any ? versionOverride : SaveUtil.GetIsG1SAV(data);
if (Version == GameVersion.Invalid)
return;

Japanese = SaveUtil.GetIsG1SAVJ(Data);
Offsets = Japanese ? SAV1Offsets.JPN : SAV1Offsets.INT;

Initialize(versionOverride);
}

private void Initialize(GameVersion versionOverride)
{
// see if RBY can be differentiated
if (Starter != 0 && versionOverride != GameVersion.Any)
Version = Yellow ? GameVersion.YW : GameVersion.RB;
Expand Down Expand Up @@ -102,9 +109,6 @@ public SAV1(byte[] data = null, GameVersion versionOverride = GameVersion.Any)

// Enable Pokedex editing
PokeDex = 0;

if (!Exportable)
ClearBoxes();
}

private readonly SAV1Offsets Offsets;
Expand Down
59 changes: 37 additions & 22 deletions PKHeX.Core/Saves/SAV2.cs
Expand Up @@ -20,34 +20,45 @@ public sealed class SAV2 : SaveFile
return 1 <= gen && gen <= 2;
}).ToArray();

public SAV2(byte[] data = null, GameVersion versionOverride = GameVersion.Any)
public SAV2(GameVersion versionOverride = GameVersion.C, LanguageID lang = LanguageID.English) : base(SaveUtil.SIZE_G2RAW_J)
{
Data = data ?? new byte[SaveUtil.SIZE_G2RAW_U];
BAK = (byte[])Data.Clone();
Exportable = !IsRangeEmpty(0, Data.Length);

if (data == null)
Version = GameVersion.C;
else if (versionOverride != GameVersion.Any)
Version = versionOverride;
else
Version = SaveUtil.GetIsG2SAV(Data);
Version = versionOverride;
switch (lang)
{
case LanguageID.Japanese:
Japanese = true;
break;
case LanguageID.Korean:
Korean = true;
break;
// otherwise, both false
}
Offsets = new SAV2Offsets(this);
Initialize();
ClearBoxes();
}

public SAV2(byte[] data, GameVersion versionOverride = GameVersion.Any) : base(data)
{
Version = versionOverride != GameVersion.Any ? versionOverride : SaveUtil.GetIsG2SAV(Data);
if (Version == GameVersion.Invalid)
return;

Japanese = SaveUtil.GetIsG2SAVJ(Data) != GameVersion.Invalid;
if (!Japanese)
Korean = SaveUtil.GetIsG2SAVK(Data) != GameVersion.Invalid;

Offsets = new SAV2Offsets(this);
Initialize();
}

private void Initialize()
{
Box = Data.Length;
Array.Resize(ref Data, Data.Length + SIZE_RESERVED);
Party = GetPartyOffset(0);

Personal = Version == GameVersion.GS ? PersonalTable.GS : PersonalTable.C;

Offsets = new SAV2Offsets(this);

LegalItems = Legal.Pouch_Items_GSC;
LegalBalls = Legal.Pouch_Ball_GSC;
LegalKeyItems = Version == GameVersion.C ? Legal.Pouch_Key_C : Legal.Pouch_Key_GS;
Expand Down Expand Up @@ -102,13 +113,17 @@ public SAV2(byte[] data = null, GameVersion versionOverride = GameVersion.Any)
{
int offset = Offsets.Daycare;

DaycareFlags[0] = Data[offset]; offset++;
DaycareFlags[0] = Data[offset];
offset++;
var pk1 = ReadPKMFromOffset(offset); // parent 1
var daycare1 = new PokeList2(pk1);
offset += (StringLength * 2) + 0x20; // nick/ot/pkm
DaycareFlags[1] = Data[offset]; offset++;
byte steps = Data[offset]; offset++;
byte BreedMotherOrNonDitto = Data[offset]; offset++;
DaycareFlags[1] = Data[offset];
offset++;
byte steps = Data[offset];
offset++;
byte BreedMotherOrNonDitto = Data[offset];
offset++;
var pk2 = ReadPKMFromOffset(offset); // parent 2
var daycare2 = new PokeList2(pk2);
offset += (StringLength * 2) + PKX.SIZE_2STORED; // nick/ot/pkm
Expand All @@ -126,9 +141,6 @@ public SAV2(byte[] data = null, GameVersion versionOverride = GameVersion.Any)
PokeDex = 0;
EventFlag = Offsets.EventFlag;
EventConst = Offsets.EventConst;

if (!Exportable)
ClearBoxes();
}

private PK2 ReadPKMFromOffset(int offset)
Expand Down Expand Up @@ -405,7 +417,10 @@ public uint Coin
}
}

private readonly ushort[] LegalItems, LegalKeyItems, LegalBalls, LegalTMHMs;
private ushort[] LegalItems;
private ushort[] LegalKeyItems;
private ushort[] LegalBalls;
private ushort[] LegalTMHMs;

public override InventoryPouch[] Inventory
{
Expand Down

0 comments on commit 1b02819

Please sign in to comment.