using System; using System.Collections.Generic; namespace PKHeX.Core { /// /// Pokémon Stadium 2 (Pokémon Stadium GS in Japan) /// public sealed class SAV2Stadium : SAV_STADIUM { 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; public override IReadOnlyList HeldItems => Legal.HeldItems_GSC; public override GameVersion Version { get; protected set; } = GameVersion.Stadium2; public override SaveFile Clone() => new SAV1Stadium((byte[])Data.Clone(), Japanese); public override int Generation => 2; private const int StringLength = 12; public override int OTLength => StringLength; public override int NickLength => StringLength; public override int BoxCount => Japanese ? 9 : 14; public override int BoxSlotCount => Japanese ? 30 : 20; public override int MaxMoveID => Legal.MaxMoveID_2; public override int MaxSpeciesID => Legal.MaxSpeciesID_2; public override int MaxAbilityID => Legal.MaxAbilityID_2; public override int MaxItemID => Legal.MaxItemID_2; public override Type PKMType => typeof(SK2); public override PKM BlankPKM => new SK2(Japanese); protected override PKM GetPKM(byte[] data) => new SK2(data, Japanese); private const int SIZE_SK2 = PokeCrypto.SIZE_2STADIUM; // 60 protected override int SIZE_STORED => SIZE_SK2; protected override int SIZE_PARTY => SIZE_SK2; 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)) { Box = BoxStart; } public SAV2Stadium(bool japanese = false) : base(japanese, SaveUtil.SIZE_G1STAD) { Box = BoxStart; ClearBoxes(); } protected override bool GetIsBoxChecksumValid(int i) { 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; } protected override void SetBoxMetadata(int i) { var bdata = GetBoxOffset(i); // Set box count int count = 0; for (int s = 0; s < BoxSlotCount; s++) { var rel = bdata + (SIZE_STORED * s); if (Data[rel] != 0) // Species present count++; } var boxOfs = bdata - ListHeaderSizeBox; if (Data[boxOfs] == 0) { Data[boxOfs] = 1; 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; } } protected override void SetBoxChecksum(int i) { var boxOfs = GetBoxOffset(i) - ListHeaderSizeBox; var size = BoxSize - 2; var chk = Checksums.CheckSum16(Data, boxOfs, size); BigEndian.GetBytes(chk).CopyTo(Data, boxOfs + size); } public static int GetTeamOffset(Stadium2TeamType type, int team) { if ((uint)team >= TeamCountType) throw new ArgumentOutOfRangeException(nameof(team)); var index = (TeamCountType * (int)type) + team; return GetTeamOffset(index); } public static int GetTeamOffset(int team) { if (team < 40) return 0 + (team * TeamSize); // Teams 41-60 are in a separate chunk return 0x4000 + ((team - 40) * TeamSize); } public string GetTeamName(int team) { var name = $"{((Stadium2TeamType) (team / TeamCountType)).ToString().Replace('_', ' ')} {(team % 10) + 1}"; var ofs = GetTeamOffset(team); var str = GetString(ofs + 4, 7); if (string.IsNullOrWhiteSpace(str)) return name; var id = BigEndian.ToUInt16(Data, ofs + 2); return $"{name} [{id:D5}:{str}]"; } public override string GetBoxName(int box) { var ofs = GetBoxOffset(box) - 0x10; var str = GetString(ofs, 0x10); if (string.IsNullOrWhiteSpace(str)) return $"Box {box + 1}"; return str; } public override SlotGroup GetTeam(int team) { if ((uint)team >= TeamCount) throw new ArgumentOutOfRangeException(nameof(team)); var name = GetTeamName(team); var members = new SK2[6]; var ofs = GetTeamOffset(team); for (int i = 0; i < 6; i++) { var rel = ofs + ListHeaderSizeTeam + (i * SIZE_STORED); members[i] = (SK2)GetStoredSlot(Data, rel); } return new SlotGroup(name, members); } public override int GetBoxOffset(int box) { if (box == 0) return BoxStart + ListHeaderSizeBox; return BoxContinue + ListHeaderSizeBox + ((box - 1) * BoxSize); } public static bool IsStadium(byte[] data) { if (data.Length != SaveUtil.SIZE_G2STAD) return false; return StadiumUtil.IsMagicPresentEither(data, TeamSize, MAGIC_FOOTER); } // Check Box 1's footer magic. private static bool IsStadiumJ(byte[] data) => StadiumUtil.IsMagicPresentAbsolute(data, BoxStart + BoxSizeJ - ListFooterSize, MAGIC_FOOTER); } public enum Stadium2TeamType { Anything_Goes = 0, Little_Cup = 1, Poké_Cup = 2, Prime_Cup = 3, GymLeader_Castle = 4, Vs_Rival = 5, } }