diff --git a/PKHeX.Core/Game/Enums/GameVersion.cs b/PKHeX.Core/Game/Enums/GameVersion.cs index 71745586b..6f02718de 100644 --- a/PKHeX.Core/Game/Enums/GameVersion.cs +++ b/PKHeX.Core/Game/Enums/GameVersion.cs @@ -448,6 +448,12 @@ public enum GameVersion /// GBCartEraOnly, + /// + /// Pocket Monsters Stadium data origin identifier + /// + /// + StadiumJ, + /// /// Pokémon Stadium data origin identifier /// diff --git a/PKHeX.Core/Saves/SAV1StadiumJ.cs b/PKHeX.Core/Saves/SAV1StadiumJ.cs new file mode 100644 index 000000000..d2a636eb4 --- /dev/null +++ b/PKHeX.Core/Saves/SAV1StadiumJ.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; + +namespace PKHeX.Core +{ + /// + /// Pocket Monsters Stadium + /// + public class SAV1StadiumJ : SaveFile + { + protected override string BAKText => $"{OT} ({Version}) - {PlayTimeString}"; + public override string Filter => "SAV File|*.sav|All Files|*.*"; + public override string Extension => ".sav"; + + public override PersonalTable Personal => PersonalTable.Y; + public override int MaxEV => ushort.MaxValue; + public override IReadOnlyList HeldItems => Array.Empty(); + public override GameVersion Version { get; protected set; } = GameVersion.StadiumJ; + + public override SaveFile Clone() => new SAV1StadiumJ((byte[])Data.Clone()); + + public override bool ChecksumsValid => true; + public override string ChecksumInfo => string.Empty; + 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 int StringLength => 5; + public override int OTLength => StringLength; + public override int NickLength => StringLength; + public override int BoxCount => 8; + public override int BoxSlotCount => 30; + + public override int MaxMoveID => Legal.MaxMoveID_1; + 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; + protected override void SetChecksums() { } // todo + + public override Type PKMType => typeof(PK1); + + protected override PKM GetPKM(byte[] data) + { + int len = StringLength; + var nick = data.Slice(0x21, len); + var ot = data.Slice(0x21 + len, len); + 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); + protected override int SIZE_STORED => 0x2D; + protected override int SIZE_PARTY => 0x2D; + + public SAV1StadiumJ(byte[] data) : base(data, false) + { + Box = 0x2500; + } + + public SAV1StadiumJ() : base(SaveUtil.SIZE_G1STAD) + { + Box = 0x2500; + ClearBoxes(); + } + + private const int TeamSizeJ = 0x128; + private const int BoxSizeJ = 0x560; + public override int GetBoxOffset(int box) => (box * BoxSizeJ) + 0x14; + 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 + // set this value at this time + ((PK1)pkm).Stat_LevelBox = pkm.CurrentLevel; + base.WriteSlotFormatStored(pkm, Data, offset); + } + + public override void WriteBoxSlot(PKM pkm, byte[] data, int offset) + { + // pkm that have never been boxed have yet to save the 'current level' for box indication + // set this value at this time + ((PK1)pkm).Stat_LevelBox = pkm.CurrentLevel; + base.WriteBoxSlot(pkm, Data, offset); + } + + public static bool IsStadiumJ(byte[] data) + { + if (data.Length != SaveUtil.SIZE_G1STAD) + return false; + + for (int i = 0; i < 10; i++) + { + if (BitConverter.ToUInt32(data, 0x11A + (i * TeamSizeJ)) != 0x454B4F50) // POKE + return false; + } + return true; + } + } +} diff --git a/PKHeX.Core/Saves/Util/SaveUtil.cs b/PKHeX.Core/Saves/Util/SaveUtil.cs index c3e40ac79..4de02d0d7 100644 --- a/PKHeX.Core/Saves/Util/SaveUtil.cs +++ b/PKHeX.Core/Saves/Util/SaveUtil.cs @@ -123,6 +123,8 @@ private static GameVersion GetSAVType(byte[] data) return DPPt; if (SAV1Stadium.IsStadiumU(data) || SAV1Stadium.IsStadiumJ(data)) return Stadium; + if (SAV1StadiumJ.IsStadiumJ(data)) + return StadiumJ; if ((ver = GetIsG8SAV(data)) != Invalid) return ver; @@ -528,6 +530,7 @@ private static GameVersion GetIsG8SAV(byte[] data) case Gen4: return new Bank4(data); case Gen7: return Bank7.GetBank7(data); case Stadium: return new SAV1Stadium(data); + case StadiumJ: return new SAV1StadiumJ(data); // No pattern matched default: return null;