Refactoring: Finalize Stadium savefile logic

Extract common abstract class for shared logic
Clean up property/field/method ordering to be consistent and logical (roughly: attributes, constructors, state management, retrievable values, static methods)
Apply default language OT name
This commit is contained in:
Kurt 2020-10-04 09:23:16 -07:00
parent d13b893351
commit 3fd6817a42
5 changed files with 274 additions and 351 deletions

View file

@ -3,16 +3,13 @@ using System.Collections.Generic;
namespace PKHeX.Core
{
public sealed class SAV1Stadium : SaveFile, ILangDeviantSave
/// <summary>
/// Pokémon Stadium (Pokémon Stadium 2 in Japan)
/// </summary>
public sealed class SAV1Stadium : SAV_STADIUM
{
protected override string BAKText => $"{OT} ({Version})";
public override string Filter => "SAV File|*.sav|All Files|*.*";
public override string Extension => ".sav";
public int SaveRevision => Japanese ? 0 : 1;
public string SaveRevisionString => Japanese ? "J" : "U";
public bool Japanese { get; }
public bool Korean => false;
public override int SaveRevision => Japanese ? 0 : 1;
public override string SaveRevisionString => Japanese ? "J" : "U";
public override PersonalTable Personal => PersonalTable.Y;
public override int MaxEV => ushort.MaxValue;
@ -21,18 +18,7 @@ namespace PKHeX.Core
public override SaveFile Clone() => new SAV1Stadium((byte[])Data.Clone(), Japanese);
public override string ChecksumInfo => ChecksumsValid ? "Checksum valid." : "Checksum invalid";
public override int Generation => 1;
public override string GetString(byte[] data, int offset, int length) => StringConverter12.GetString1(data, offset, length, Japanese);
public override byte[] SetString(string value, int maxLength, int PadToSize = 0, ushort PadWith = 0)
{
if (PadToSize == 0)
PadToSize = maxLength + 1;
return StringConverter12.SetString1(value, maxLength, Japanese, PadToSize, PadWith);
}
private int StringLength => Japanese ? StringLengthJ : StringLengthU;
private const int StringLengthJ = 6;
private const int StringLengthU = 11;
@ -45,53 +31,64 @@ namespace PKHeX.Core
public override int MaxSpeciesID => Legal.MaxSpeciesID_1;
public override int MaxAbilityID => Legal.MaxAbilityID_1;
public override int MaxItemID => Legal.MaxItemID_1;
public override int MaxBallID => 0; // unused
public override int MaxGameID => 99; // unused
public override int MaxMoney => 999999;
public override int MaxCoins => 9999;
public override int GetPartyOffset(int slot) => -1;
public override Type PKMType => typeof(PK1);
public override PKM BlankPKM => new PK1(Japanese);
private const int SIZE_PK1J = PokeCrypto.SIZE_1STORED + (2 * StringLengthJ); // 0x2D
private const int SIZE_PK1U = PokeCrypto.SIZE_1STORED + (2 * StringLengthU); // 0x37
protected override int SIZE_STORED => Japanese ? SIZE_PK1J : SIZE_PK1U;
protected override int SIZE_PARTY => Japanese ? SIZE_PK1J : SIZE_PK1U;
private readonly bool IsPairSwapped;
private int ListHeaderSize => Japanese ? 0x0C : 0x10;
private const int ListFooterSize = 6; // POKE + 2byte checksum
protected override byte[] GetFinalData()
private const int TeamCountU = 10;
private const int TeamCountJ = 12;
private const int TeamCountTypeU = 9; // team-types 1 & 2 are unused
private const int TeamCountTypeJ = 9;
protected override int TeamCount => Japanese ? TeamCountJ * TeamCountTypeJ : TeamCountU * TeamCountTypeU;
private const int TeamSizeJ = 0x0C + (SIZE_PK1J * 6) + ListFooterSize; // 0x120
private const int TeamSizeU = 0x10 + (SIZE_PK1U * 6) + ListFooterSize; // 0x160
private const uint FOOTER_MAGIC = 0x454B4F50; // POKE
private int BoxSize => Japanese ? BoxSizeJ : BoxSizeU;
//private int ListHeaderSizeBox => Japanese ? 0x0C : 0x10;
private const int BoxSizeJ = 0x0C + (SIZE_PK1J * 30) + ListFooterSize; // 0x558
private const int BoxSizeU = 0x10 + (SIZE_PK1U * 20) + 6 + ListFooterSize; // 0x468 (6 bytes alignment)
public override int GetBoxOffset(int box) => Box + ListHeaderSize + (box * BoxSize);
public SAV1Stadium(byte[] data) : this(data, IsStadiumJ(data)) { }
public SAV1Stadium(byte[] data, bool japanese) : base(data, japanese, StadiumUtil.IsMagicPresentSwap(data, japanese ? TeamSizeJ : TeamSizeU, FOOTER_MAGIC))
{
var result = base.GetFinalData();
if (IsPairSwapped)
BigEndian.SwapBytes32(result = (byte[])result.Clone());
return result;
Box = 0xC000;
}
public override bool ChecksumsValid => GetBoxChecksumsValid();
protected override void SetChecksums() => SetBoxChecksums();
private bool GetBoxChecksumsValid()
public SAV1Stadium(bool japanese = false) : base(japanese, SaveUtil.SIZE_G1STAD)
{
for (int i = 0; i < BoxCount; i++)
{
var boxOfs = GetBoxOffset(i) - ListHeaderSize;
var size = BoxSize - 2;
var chk = Checksums.CheckSum16(Data, boxOfs, size);
var actual = BigEndian.ToUInt16(Data, boxOfs + size);
if (chk != actual)
return false;
}
return true;
Box = 0xC000;
ClearBoxes();
}
private void SetBoxChecksums()
protected override bool GetIsBoxChecksumValid(int i)
{
for (int i = 0; i < BoxCount; i++)
{
SetBoxMetadata(i);
var boxOfs = GetBoxOffset(i) - ListHeaderSize;
var size = BoxSize - 2;
var chk = Checksums.CheckSum16(Data, boxOfs, size);
BigEndian.GetBytes(chk).CopyTo(Data, boxOfs + size);
}
var boxOfs = GetBoxOffset(i) - ListHeaderSize;
var size = BoxSize - 2;
var chk = Checksums.CheckSum16(Data, boxOfs, size);
var actual = BigEndian.ToUInt16(Data, boxOfs + size);
return chk == actual;
}
private void SetBoxMetadata(int i)
protected override void SetBoxChecksum(int i)
{
var boxOfs = GetBoxOffset(i) - ListHeaderSize;
var size = BoxSize - 2;
var chk = Checksums.CheckSum16(Data, boxOfs, size);
BigEndian.GetBytes(chk).CopyTo(Data, boxOfs + size);
}
protected override void SetBoxMetadata(int i)
{
var bdata = GetBoxOffset(i);
@ -108,58 +105,15 @@ namespace PKHeX.Core
Data[bdata - 1] = (byte)count;
}
public override Type PKMType => typeof(PK1);
protected override PKM GetPKM(byte[] data)
{
int len = StringLength;
var nick = data.Slice(PokeCrypto.SIZE_1STORED, len);
var ot = data.Slice(PokeCrypto.SIZE_1STORED + len, len);
data = data.Slice(0, PokeCrypto.SIZE_1STORED);
return new PK1(data, Japanese) {OT_Trash = ot, Nickname_Trash = nick};
return new PK1(data, Japanese) { OT_Trash = ot, Nickname_Trash = nick };
}
protected override byte[] DecryptPKM(byte[] data) => data;
public override PKM BlankPKM => new PK1(Japanese);
private const int SIZE_PK1J = PokeCrypto.SIZE_1STORED + (2 * StringLengthJ); // 0x2D
private const int SIZE_PK1U = PokeCrypto.SIZE_1STORED + (2 * StringLengthU); // 0x37
protected override int SIZE_STORED => Japanese ? SIZE_PK1J : SIZE_PK1U;
protected override int SIZE_PARTY => Japanese ? SIZE_PK1J : SIZE_PK1U;
public SAV1Stadium(byte[] data) : this(data, IsStadiumJ(data)) { }
public SAV1Stadium(byte[] data, bool japanese) : base(data)
{
Japanese = japanese;
var swap = StadiumUtil.IsMagicPresentSwap(data, TeamSize, MAGIC_POKE);
if (swap)
{
BigEndian.SwapBytes32(Data);
IsPairSwapped = true;
}
Box = 0xC000;
}
public SAV1Stadium(bool japanese = false) : base(SaveUtil.SIZE_G1STAD)
{
Japanese = japanese;
Box = 0xC000;
ClearBoxes();
}
private int ListHeaderSize => Japanese ? 0x0C : 0x10;
private const int ListFooterSize = 6; // POKE + 2byte checksum
private const int TeamCountU = 10;
private const int TeamCountJ = 12;
private const int TeamCountTypeU = 9; // team-types 1 & 2 are unused
private const int TeamCountTypeJ = 9;
private int TeamCount => Japanese ? TeamCountJ * TeamCountTypeJ : TeamCountU * TeamCountTypeU;
private int TeamSize => Japanese ? TeamSizeJ : TeamSizeU;
private const int TeamSizeJ = 0x0C + (SIZE_PK1J * 6) + ListFooterSize; // 0x120
private const int TeamSizeU = 0x10 + (SIZE_PK1U * 6) + ListFooterSize; // 0x160
public int GetTeamOffset(int team) => Japanese ? GetTeamOffsetJ(team) : GetTeamOffsetU(team);
private int GetTeamOffsetJ(int team)
@ -239,11 +193,9 @@ namespace PKHeX.Core
};
}
public SlotGroup[] GetRegisteredTeams()
public override SlotGroup[] GetRegisteredTeams()
{
var result = new SlotGroup[TeamCount];
for (int i = 0; i < result.Length; i++)
result[i] = GetTeam(i);
var result = base.GetRegisteredTeams();
if (Japanese)
return result;
@ -254,7 +206,7 @@ namespace PKHeX.Core
return noUnused;
}
public SlotGroup GetTeam(int team)
public override SlotGroup GetTeam(int team)
{
if ((uint)team >= TeamCount)
throw new ArgumentOutOfRangeException(nameof(team));
@ -270,14 +222,6 @@ namespace PKHeX.Core
return new SlotGroup(name, members);
}
private int BoxSize => Japanese ? BoxSizeJ : BoxSizeU;
private int ListHeaderSizeBox => Japanese ? 0x0C : 0x10;
private const int BoxSizeJ = 0x0C + (SIZE_PK1J * 30) + ListFooterSize; // 0x558
private const int BoxSizeU = 0x10 + (SIZE_PK1U * 20) + 6 + ListFooterSize; // 0x468 (6 bytes alignment)
public override int GetBoxOffset(int box) => Box + ListHeaderSize + (box * BoxSize);
public override string GetBoxName(int box) => $"Box {box + 1}";
public override void SetBoxName(int box, string value) { }
public override void WriteSlotFormatStored(PKM pkm, byte[] data, int offset)
{
// pkm that have never been boxed have yet to save the 'current level' for box indication
@ -294,21 +238,19 @@ namespace PKHeX.Core
base.WriteBoxSlot(pkm, Data, offset);
}
private const uint MAGIC_POKE = 0x454B4F50;
public static bool IsStadiumU(byte[] data)
public static bool IsStadium(byte[] data)
{
if (data.Length != SaveUtil.SIZE_G1STAD)
return false;
return StadiumUtil.IsMagicPresentEither(data, TeamSizeU, MAGIC_POKE);
if (IsStadiumU(data))
return true;
if (IsStadiumJ(data))
return true;
return false;
}
public static bool IsStadiumJ(byte[] data)
{
if (data.Length != SaveUtil.SIZE_G1STAD)
return false;
return StadiumUtil.IsMagicPresentEither(data, TeamSizeJ, MAGIC_POKE);
}
private static bool IsStadiumU(byte[] data) => StadiumUtil.IsMagicPresentEither(data, TeamSizeU, FOOTER_MAGIC);
private static bool IsStadiumJ(byte[] data) => StadiumUtil.IsMagicPresentEither(data, TeamSizeJ, FOOTER_MAGIC);
}
public enum Stadium1TeamType

View file

@ -6,17 +6,11 @@ namespace PKHeX.Core
/// <summary>
/// Pocket Monsters Stadium
/// </summary>
public class SAV1StadiumJ : SaveFile, ILangDeviantSave
public sealed class SAV1StadiumJ : SAV_STADIUM
{
protected override string BAKText => $"{OT} ({Version})";
public override string Filter => "SAV File|*.sav|All Files|*.*";
public override string Extension => ".sav";
// Required since PK1 logic comparing a save file assumes the save file can be U/J
public int SaveRevision => 0;
public string SaveRevisionString => "0"; // so we're different from Japanese SAV1Stadium naming...
public bool Japanese => true;
public bool Korean => false;
public override int SaveRevision => 0;
public override string SaveRevisionString => "0"; // so we're different from Japanese SAV1Stadium naming...
public override PersonalTable Personal => PersonalTable.Y;
public override int MaxEV => ushort.MaxValue;
@ -25,18 +19,7 @@ namespace PKHeX.Core
public override SaveFile Clone() => new SAV1StadiumJ((byte[])Data.Clone());
public override string ChecksumInfo => ChecksumsValid ? "Checksum valid." : "Checksum invalid";
public override int Generation => 1;
public override string GetString(byte[] data, int offset, int length) => StringConverter12.GetString1(data, offset, length, true);
public override byte[] SetString(string value, int maxLength, int PadToSize = 0, ushort PadWith = 0)
{
if (PadToSize == 0)
PadToSize = maxLength + 1;
return StringConverter12.SetString1(value, maxLength, true, PadToSize, PadWith);
}
private const int StringLength = 6; // Japanese Only
public override int OTLength => StringLength;
public override int NickLength => StringLength;
@ -47,52 +30,53 @@ namespace PKHeX.Core
public override int MaxSpeciesID => Legal.MaxSpeciesID_1;
public override int MaxAbilityID => Legal.MaxAbilityID_1;
public override int MaxItemID => Legal.MaxItemID_1;
public override int MaxBallID => 0; // unused
public override int MaxGameID => 99; // unused
public override int MaxMoney => 999999;
public override int MaxCoins => 9999;
public override int GetPartyOffset(int slot) => -1;
private readonly bool IsPairSwapped;
protected override byte[] GetFinalData()
{
var result = base.GetFinalData();
if (IsPairSwapped)
BigEndian.SwapBytes32(result = (byte[])result.Clone());
return result;
}
public override bool ChecksumsValid => GetBoxChecksumsValid();
protected override void SetChecksums() => SetBoxChecksums();
private bool GetBoxChecksumsValid()
{
for (int i = 0; i < BoxCount; i++)
{
var boxOfs = GetBoxOffset(i) - ListHeaderSize;
const int size = BoxSizeJ - 2;
var chk = Checksums.CheckSum16(Data, boxOfs, size);
var actual = BigEndian.ToUInt16(Data, boxOfs + size);
if (chk != actual)
return false;
}
return true;
}
private void SetBoxChecksums()
{
for (int i = 0; i < BoxCount; i++)
{
var boxOfs = GetBoxOffset(i) - ListHeaderSize;
const int size = BoxSizeJ - 2;
var chk = Checksums.CheckSum16(Data, boxOfs, size);
BigEndian.GetBytes(chk).CopyTo(Data, boxOfs + size);
}
}
private const int SIZE_PK1J = PokeCrypto.SIZE_1STORED + (2 * StringLength); // 0x2D
protected override int SIZE_STORED => SIZE_PK1J;
protected override int SIZE_PARTY => SIZE_PK1J;
public override Type PKMType => typeof(PK1);
public override PKM BlankPKM => new PK1(true);
private const int ListHeaderSize = 0x14;
private const int ListFooterSize = 6; // POKE + 2byte checksum
private const uint FOOTER_MAGIC = 0x454B4F50; // POKE
protected override int TeamCount => 16; // 32 teams stored sequentially; latter 16 are backups
private const int TeamSizeJ = 0x14 + (SIZE_PK1J * 6) + ListFooterSize; // 0x128
private const int BoxSizeJ = 0x560;
public SAV1StadiumJ(byte[] data) : base(data, true, StadiumUtil.IsMagicPresentSwap(data, TeamSizeJ, FOOTER_MAGIC))
{
Box = 0x2500;
}
public SAV1StadiumJ() : base(true, SaveUtil.SIZE_G1STAD)
{
Box = 0x2500;
ClearBoxes();
}
protected override bool GetIsBoxChecksumValid(int i)
{
var boxOfs = GetBoxOffset(i) - ListHeaderSize;
const int size = BoxSizeJ - 2;
var chk = Checksums.CheckSum16(Data, boxOfs, size);
var actual = BigEndian.ToUInt16(Data, boxOfs + size);
return chk == actual;
}
protected override void SetBoxChecksum(int i)
{
var boxOfs = GetBoxOffset(i) - ListHeaderSize;
const int size = BoxSizeJ - 2;
var chk = Checksums.CheckSum16(Data, boxOfs, size);
BigEndian.GetBytes(chk).CopyTo(Data, boxOfs + size);
}
protected override void SetBoxMetadata(int i)
{
// Not implemented
}
protected override PKM GetPKM(byte[] data)
{
@ -103,47 +87,11 @@ namespace PKHeX.Core
return new PK1(data, true) { OT_Trash = ot, Nickname_Trash = nick };
}
protected override byte[] DecryptPKM(byte[] data) => data;
public override PKM BlankPKM => new PK1(true);
private const int SIZE_PK1J = PokeCrypto.SIZE_1STORED + (2 * StringLength); // 0x2D
protected override int SIZE_STORED => SIZE_PK1J;
protected override int SIZE_PARTY => SIZE_PK1J;
public SAV1StadiumJ(byte[] data) : base(data)
{
var swap = StadiumUtil.IsMagicPresentSwap(data, TeamSizeJ, MAGIC_POKE);
if (swap)
{
BigEndian.SwapBytes32(Data);
IsPairSwapped = true;
}
Box = 0x2500;
}
public SAV1StadiumJ() : base(SaveUtil.SIZE_G1STAD)
{
Box = 0x2500;
ClearBoxes();
}
private const int ListHeaderSize = 0x14;
private const int ListFooterSize = 6; // POKE + 2byte checksum
private const int TeamCount = 16; // 32 teams stored sequentially; latter 16 are backups
private const int TeamSizeJ = 0x14 + (SIZE_PK1J * 6) + ListFooterSize; // 0x128
public override int GetBoxOffset(int box) => Box + ListHeaderSize + (box * BoxSizeJ);
public static int GetTeamOffset(int team) => 0 + ListHeaderSize + (team * TeamSizeJ);
public static string GetTeamName(int team) => $"Team {team + 1}";
public SlotGroup[] GetRegisteredTeams()
{
var result = new SlotGroup[TeamCount];
for (int i = 0; i < result.Length; i++)
result[i] = GetTeam(i);
return result;
}
public SlotGroup GetTeam(int team)
public override SlotGroup GetTeam(int team)
{
if ((uint)team >= TeamCount)
throw new ArgumentOutOfRangeException(nameof(team));
@ -159,11 +107,6 @@ namespace PKHeX.Core
return new SlotGroup(name, members);
}
private const int BoxSizeJ = 0x560;
public override int GetBoxOffset(int box) => Box + ListHeaderSize + (box * BoxSizeJ);
public override string GetBoxName(int box) => $"Box {box + 1}";
public override void SetBoxName(int box, string value) { }
public override void WriteSlotFormatStored(PKM pkm, byte[] data, int offset)
{
// pkm that have never been boxed have yet to save the 'current level' for box indication
@ -180,13 +123,11 @@ namespace PKHeX.Core
base.WriteBoxSlot(pkm, Data, offset);
}
private const uint MAGIC_POKE = 0x454B4F50;
public static bool IsStadiumJ(byte[] data)
public static bool IsStadium(byte[] data)
{
if (data.Length != SaveUtil.SIZE_G1STADJ)
return false;
return StadiumUtil.IsMagicPresentEither(data, TeamSizeJ, MAGIC_POKE);
return StadiumUtil.IsMagicPresentEither(data, TeamSizeJ, FOOTER_MAGIC);
}
}
}

View file

@ -3,16 +3,13 @@ using System.Collections.Generic;
namespace PKHeX.Core
{
public sealed class SAV2Stadium : SaveFile, ILangDeviantSave
/// <summary>
/// Pokémon Stadium 2 (Pokémon Stadium GS in Japan)
/// </summary>
public sealed class SAV2Stadium : SAV_STADIUM
{
protected override string BAKText => $"{OT} ({Version})";
public override string Filter => "SAV File|*.sav|All Files|*.*";
public override string Extension => ".sav";
public int SaveRevision => Japanese ? 0 : 1;
public string SaveRevisionString => Japanese ? "J" : "U";
public bool Japanese { get; }
public bool Korean => false;
public override int SaveRevision => Japanese ? 0 : 1;
public override string SaveRevisionString => Japanese ? "J" : "U";
public override PersonalTable Personal => PersonalTable.C;
public override int MaxEV => ushort.MaxValue;
@ -21,18 +18,7 @@ namespace PKHeX.Core
public override SaveFile Clone() => new SAV1Stadium((byte[])Data.Clone(), Japanese);
public override string ChecksumInfo => ChecksumsValid ? "Checksum valid." : "Checksum invalid";
public override int Generation => 2;
public override string GetString(byte[] data, int offset, int length) => StringConverter12.GetString1(data, offset, length, Japanese);
public override byte[] SetString(string value, int maxLength, int PadToSize = 0, ushort PadWith = 0)
{
if (PadToSize == 0)
PadToSize = maxLength + 1;
return StringConverter12.SetString1(value, maxLength, Japanese, PadToSize, PadWith);
}
private const int StringLength = 12;
public override int OTLength => StringLength;
public override int NickLength => StringLength;
@ -43,53 +29,56 @@ namespace PKHeX.Core
public override int MaxSpeciesID => Legal.MaxSpeciesID_2;
public override int MaxAbilityID => Legal.MaxAbilityID_2;
public override int MaxItemID => Legal.MaxItemID_2;
public override int MaxBallID => 0; // unused
public override int MaxGameID => 99; // unused
public override int MaxMoney => 999999;
public override int MaxCoins => 9999;
public override int GetPartyOffset(int slot) => -1;
public override Type PKMType => typeof(SK2);
public override PKM BlankPKM => new SK2(Japanese);
protected override PKM GetPKM(byte[] data) => new SK2(data, Japanese);
private readonly bool IsPairSwapped;
private const int SIZE_SK2 = PokeCrypto.SIZE_2STADIUM; // 60
protected override int SIZE_STORED => SIZE_SK2;
protected override int SIZE_PARTY => SIZE_SK2;
protected override byte[] GetFinalData()
private const int ListHeaderSizeTeam = 0x10;
private const int ListHeaderSizeBox = 0x20;
private const int ListFooterSize = 6; // POKE + 2byte checksum
protected override int TeamCount => 60;
private const int TeamCountType = 10;
private const int TeamSize = ListHeaderSizeTeam + (SIZE_SK2 * 6) + 2 + ListFooterSize; // 0x180
private int BoxSize => Japanese ? BoxSizeJ : BoxSizeU;
private const int BoxSizeJ = ListHeaderSizeBox + (SIZE_SK2 * 30) + 2 + ListFooterSize; // 0x730
private const int BoxSizeU = ListHeaderSizeBox + (SIZE_SK2 * 20) + 2 + ListFooterSize; // 0x4D8
// Box 1 is stored separately from the remainder of the boxes.
private const int BoxStart = 0x5E00; // Box 1
private const int BoxContinue = 0x8000; // Box 2+
private const uint MAGIC_FOOTER = 0x30763350; // P3v0
public SAV2Stadium(byte[] data) : this(data, IsStadiumJ(data)) { }
public SAV2Stadium(byte[] data, bool japanese) : base(data, japanese, StadiumUtil.IsMagicPresentSwap(data, TeamSize, MAGIC_FOOTER))
{
var result = base.GetFinalData();
if (IsPairSwapped)
BigEndian.SwapBytes32(result = (byte[])result.Clone());
return result;
Box = BoxStart;
}
public override bool ChecksumsValid => GetBoxChecksumsValid();
protected override void SetChecksums() => SetBoxChecksums();
private bool GetBoxChecksumsValid()
public SAV2Stadium(bool japanese = false) : base(japanese, SaveUtil.SIZE_G1STAD)
{
for (int i = 0; i < BoxCount; i++)
{
var boxOfs = GetBoxOffset(i) - ListHeaderSizeBox;
var size = BoxSize - 2;
var chk = Checksums.CheckSum16(Data, boxOfs, size);
var actual = BigEndian.ToUInt16(Data, boxOfs + size);
if (chk != actual)
return false;
}
return true;
Box = BoxStart;
ClearBoxes();
}
private void SetBoxChecksums()
protected override bool GetIsBoxChecksumValid(int i)
{
for (int i = 0; i < BoxCount; i++)
{
SetBoxMetadata(i);
var boxOfs = GetBoxOffset(i) - ListHeaderSizeBox;
var size = BoxSize - 2;
var chk = Checksums.CheckSum16(Data, boxOfs, size);
BigEndian.GetBytes(chk).CopyTo(Data, boxOfs + size);
}
var boxOfs = GetBoxOffset(i) - ListHeaderSizeBox;
var size = BoxSize - 2;
var chk = Checksums.CheckSum16(Data, boxOfs, size);
var actual = BigEndian.ToUInt16(Data, boxOfs + size);
return chk == actual;
}
private void SetBoxMetadata(int i)
protected override void SetBoxMetadata(int i)
{
var bdata = GetBoxOffset(i);
@ -106,54 +95,24 @@ namespace PKHeX.Core
if (Data[boxOfs] == 0)
{
Data[boxOfs] = 1;
Data[boxOfs + 1] = (byte) count;
Data[boxOfs + 1] = (byte)count;
Data[boxOfs + 4] = StringConverter12.G1TerminatorCode;
StringConverter12.SetString1("1234", 4, Japanese).CopyTo(Data, boxOfs + 0x10);
}
else
{
Data[boxOfs + 1] = (byte) count;
Data[boxOfs + 1] = (byte)count;
}
}
public override Type PKMType => typeof(SK2);
protected override PKM GetPKM(byte[] data) => new SK2(data, Japanese);
protected override byte[] DecryptPKM(byte[] data) => data;
public override PKM BlankPKM => new SK2(Japanese);
private const int SIZE_SK2 = PokeCrypto.SIZE_2STADIUM; // 60
protected override int SIZE_STORED => SIZE_SK2;
protected override int SIZE_PARTY => SIZE_SK2;
public SAV2Stadium(byte[] data) : this(data, IsJapanese(data)) { }
public SAV2Stadium(byte[] data, bool japanese) : base(data)
protected override void SetBoxChecksum(int i)
{
var swap = StadiumUtil.IsMagicPresentSwap(data, TeamSize, MAGIC_POKE);
if (swap)
{
BigEndian.SwapBytes32(Data);
IsPairSwapped = true;
}
Japanese = japanese;
Box = BoxStart;
var boxOfs = GetBoxOffset(i) - ListHeaderSizeBox;
var size = BoxSize - 2;
var chk = Checksums.CheckSum16(Data, boxOfs, size);
BigEndian.GetBytes(chk).CopyTo(Data, boxOfs + size);
}
public SAV2Stadium(bool japanese = false) : base(SaveUtil.SIZE_G1STAD)
{
Japanese = japanese;
Box = BoxStart;
ClearBoxes();
}
private const int ListHeaderSizeTeam = 0x10;
private const int ListHeaderSizeBox = 0x20;
private const int ListFooterSize = 6; // POKE + 2byte checksum
private const int TeamCount = 60;
private const int TeamCountType = 10;
private const int TeamSize = ListHeaderSizeTeam + (SIZE_SK2 * 6) + 2 + ListFooterSize; // 0x180
public static int GetTeamOffset(Stadium2TeamType type, int team)
{
if ((uint)team >= TeamCountType)
@ -173,15 +132,7 @@ namespace PKHeX.Core
public static string GetTeamName(int team) => $"{((Stadium2TeamType)(team / TeamCountType)).ToString().Replace('_', ' ')} {(team % 10) + 1}";
public SlotGroup[] GetRegisteredTeams()
{
var result = new SlotGroup[TeamCount];
for (int i = 0; i < result.Length; i++)
result[i] = GetTeam(i);
return result;
}
public SlotGroup GetTeam(int team)
public override SlotGroup GetTeam(int team)
{
if ((uint)team >= TeamCount)
throw new ArgumentOutOfRangeException(nameof(team));
@ -197,14 +148,6 @@ namespace PKHeX.Core
return new SlotGroup(name, members);
}
private int BoxSize => Japanese ? BoxSizeJ : BoxSizeU;
private const int BoxSizeJ = ListHeaderSizeBox + (SIZE_SK2 * 30) + 2 + ListFooterSize; // 0x730
private const int BoxSizeU = ListHeaderSizeBox + (SIZE_SK2 * 20) + 2 + ListFooterSize; // 0x4D8
// Box 1 is stored separately from the remainder of the boxes.
private const int BoxStart = 0x5E00; // Box 1
private const int BoxContinue = 0x8000; // Box 2+
public override int GetBoxOffset(int box)
{
if (box == 0)
@ -212,20 +155,15 @@ namespace PKHeX.Core
return BoxContinue + ListHeaderSizeBox + ((box - 1) * BoxSize);
}
public override string GetBoxName(int box) => $"Box {box + 1}";
public override void SetBoxName(int box, string value) { }
private const uint MAGIC_POKE = 0x30763350; // P3v0
public static bool IsStadium(byte[] data)
{
if (data.Length != SaveUtil.SIZE_G1STAD)
return false;
return StadiumUtil.IsMagicPresentEither(data, TeamSize, MAGIC_POKE);
return StadiumUtil.IsMagicPresentEither(data, TeamSize, MAGIC_FOOTER);
}
// Check Box 1's footer magic.
private static bool IsJapanese(byte[] data) => StadiumUtil.IsMagicPresentAbsolute(data, BoxStart + BoxSizeJ - ListFooterSize, MAGIC_POKE);
private static bool IsStadiumJ(byte[] data) => StadiumUtil.IsMagicPresentAbsolute(data, BoxStart + BoxSizeJ - ListFooterSize, MAGIC_FOOTER);
}
public enum Stadium2TeamType

View file

@ -0,0 +1,102 @@
namespace PKHeX.Core
{
/// <summary>
/// Base class for GB Era Stadium files.
/// </summary>
public abstract class SAV_STADIUM : SaveFile, ILangDeviantSave
{
protected sealed override string BAKText => $"{OT} ({Version})";
public sealed override string Filter => "SAV File|*.sav|All Files|*.*";
public sealed override string Extension => ".sav";
public abstract int SaveRevision { get; }
public abstract string SaveRevisionString { get; }
public bool Japanese { get; }
public bool Korean => false;
public sealed override int MaxBallID => 0; // unused
public sealed override int MaxGameID => 99; // unused
public sealed override int MaxMoney => 999999;
public sealed override int MaxCoins => 9999;
private readonly bool IsPairSwapped;
protected abstract int TeamCount { get; }
public sealed override string OT { get; set; }
public sealed override int Language => Japanese ? 1 : 2;
protected SAV_STADIUM(byte[] data, bool japanese, bool swap) : base(data)
{
Japanese = japanese;
OT = SaveUtil.GetSafeTrainerName(this, (LanguageID)Language);
if (!swap)
return;
BigEndian.SwapBytes32(Data);
IsPairSwapped = true;
}
protected SAV_STADIUM(bool japanese, int size) : base(size)
{
Japanese = japanese;
OT = SaveUtil.GetSafeTrainerName(this, (LanguageID)Language);
}
protected sealed override byte[] DecryptPKM(byte[] data) => data;
public sealed override int GetPartyOffset(int slot) => -1;
public sealed override string GetBoxName(int box) => $"Box {box + 1}";
public sealed override void SetBoxName(int box, string value) { }
public sealed override bool ChecksumsValid => GetBoxChecksumsValid();
public sealed override string ChecksumInfo => ChecksumsValid ? "Checksum valid." : "Checksum invalid";
protected abstract void SetBoxChecksum(int i);
protected abstract bool GetIsBoxChecksumValid(int i);
protected sealed override void SetChecksums() => SetBoxChecksums();
protected abstract void SetBoxMetadata(int i);
protected void SetBoxChecksums()
{
for (int i = 0; i < BoxCount; i++)
{
SetBoxMetadata(i);
SetBoxChecksum(i);
}
}
private bool GetBoxChecksumsValid()
{
for (int i = 0; i < BoxCount; i++)
{
if (!GetIsBoxChecksumValid(i))
return false;
}
return true;
}
protected sealed override byte[] GetFinalData()
{
var result = base.GetFinalData();
if (IsPairSwapped)
BigEndian.SwapBytes32(result = (byte[])result.Clone());
return result;
}
public abstract SlotGroup GetTeam(int team);
public virtual SlotGroup[] GetRegisteredTeams()
{
var result = new SlotGroup[TeamCount];
for (int i = 0; i < result.Length; i++)
result[i] = GetTeam(i);
return result;
}
public sealed override string GetString(byte[] data, int offset, int length) => StringConverter12.GetString1(data, offset, length, Japanese);
public sealed override byte[] SetString(string value, int maxLength, int PadToSize = 0, ushort PadWith = 0)
{
if (PadToSize == 0)
PadToSize = maxLength + 1;
return StringConverter12.SetString1(value, maxLength, Japanese, PadToSize, PadWith);
}
}
}

View file

@ -125,9 +125,9 @@ namespace PKHeX.Core
return DPPt;
if (SAV2Stadium.IsStadium(data))
return Stadium2;
if (SAV1Stadium.IsStadiumU(data) || SAV1Stadium.IsStadiumJ(data))
if (SAV1Stadium.IsStadium(data))
return Stadium;
if (SAV1StadiumJ.IsStadiumJ(data))
if (SAV1StadiumJ.IsStadium(data))
return StadiumJ;
if ((ver = GetIsG8SAV(data)) != Invalid)