From 3fd6817a42c0904f0673ff72fe3357580925f44e Mon Sep 17 00:00:00 2001 From: Kurt Date: Sun, 4 Oct 2020 09:23:16 -0700 Subject: [PATCH] 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 --- PKHeX.Core/Saves/SAV1Stadium.cs | 186 ++++++++++-------------------- PKHeX.Core/Saves/SAV1StadiumJ.cs | 163 +++++++++----------------- PKHeX.Core/Saves/SAV2Stadium.cs | 170 +++++++++------------------ PKHeX.Core/Saves/SAV_STADIUM.cs | 102 ++++++++++++++++ PKHeX.Core/Saves/Util/SaveUtil.cs | 4 +- 5 files changed, 274 insertions(+), 351 deletions(-) create mode 100644 PKHeX.Core/Saves/SAV_STADIUM.cs diff --git a/PKHeX.Core/Saves/SAV1Stadium.cs b/PKHeX.Core/Saves/SAV1Stadium.cs index 806069aa1..c18575ba5 100644 --- a/PKHeX.Core/Saves/SAV1Stadium.cs +++ b/PKHeX.Core/Saves/SAV1Stadium.cs @@ -3,16 +3,13 @@ namespace PKHeX.Core { - public sealed class SAV1Stadium : SaveFile, ILangDeviantSave + /// + /// Pokémon Stadium (Pokémon Stadium 2 in Japan) + /// + 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 @@ public sealed class SAV1Stadium : SaveFile, ILangDeviantSave 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 @@ public override byte[] SetString(string value, int maxLength, int PadToSize = 0, 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 @@ private void SetBoxMetadata(int i) 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 @@ private string GetTeamTypeName(int type) }; } - 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 @@ public SlotGroup[] GetRegisteredTeams() 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 @@ public SlotGroup GetTeam(int team) 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 @@ public override void WriteBoxSlot(PKM pkm, byte[] data, int offset) 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 diff --git a/PKHeX.Core/Saves/SAV1StadiumJ.cs b/PKHeX.Core/Saves/SAV1StadiumJ.cs index e60c8285e..6eed45edf 100644 --- a/PKHeX.Core/Saves/SAV1StadiumJ.cs +++ b/PKHeX.Core/Saves/SAV1StadiumJ.cs @@ -6,17 +6,11 @@ namespace PKHeX.Core /// /// Pocket Monsters Stadium /// - 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 @@ public class SAV1StadiumJ : SaveFile, ILangDeviantSave 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 @@ public override byte[] SetString(string value, int maxLength, int PadToSize = 0, 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 @@ protected override PKM GetPKM(byte[] data) 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 @@ public SlotGroup GetTeam(int team) 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 @@ public override void WriteBoxSlot(PKM pkm, byte[] data, int offset) 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); } } } diff --git a/PKHeX.Core/Saves/SAV2Stadium.cs b/PKHeX.Core/Saves/SAV2Stadium.cs index c84c91f16..cd5ed380b 100644 --- a/PKHeX.Core/Saves/SAV2Stadium.cs +++ b/PKHeX.Core/Saves/SAV2Stadium.cs @@ -3,16 +3,13 @@ namespace PKHeX.Core { - public sealed class SAV2Stadium : SaveFile, ILangDeviantSave + /// + /// Pokémon Stadium 2 (Pokémon Stadium GS in Japan) + /// + 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 @@ public sealed class SAV2Stadium : SaveFile, ILangDeviantSave 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 @@ public override byte[] SetString(string value, int maxLength, int PadToSize = 0, 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 @@ private void SetBoxMetadata(int i) 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 @@ public static int GetTeamOffset(int team) 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 @@ public SlotGroup GetTeam(int team) 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 @@ public override int GetBoxOffset(int box) 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 diff --git a/PKHeX.Core/Saves/SAV_STADIUM.cs b/PKHeX.Core/Saves/SAV_STADIUM.cs new file mode 100644 index 000000000..17d0f5ff2 --- /dev/null +++ b/PKHeX.Core/Saves/SAV_STADIUM.cs @@ -0,0 +1,102 @@ +namespace PKHeX.Core +{ + /// + /// Base class for GB Era Stadium files. + /// + 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); + } + } +} diff --git a/PKHeX.Core/Saves/Util/SaveUtil.cs b/PKHeX.Core/Saves/Util/SaveUtil.cs index 401007f8d..02b658051 100644 --- a/PKHeX.Core/Saves/Util/SaveUtil.cs +++ b/PKHeX.Core/Saves/Util/SaveUtil.cs @@ -125,9 +125,9 @@ private static GameVersion GetSAVType(byte[] data) 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)