mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-15 00:37:11 +00:00
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:
parent
d13b893351
commit
3fd6817a42
5 changed files with 274 additions and 351 deletions
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
102
PKHeX.Core/Saves/SAV_STADIUM.cs
Normal file
102
PKHeX.Core/Saves/SAV_STADIUM.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue