From 741fbd52964293a58745fb778a935ffbce36465e Mon Sep 17 00:00:00 2001 From: Kurt Date: Wed, 9 Sep 2020 22:44:46 -0700 Subject: [PATCH] Add stadium sav readonly support of registered teams no checksum logic performed, just a WIP preview --- PKHeX.Core/Saves/SAV1Stadium.cs | 131 ++++++++++++++++++++++++++++++ PKHeX.Core/Saves/Util/SaveUtil.cs | 4 + 2 files changed, 135 insertions(+) create mode 100644 PKHeX.Core/Saves/SAV1Stadium.cs diff --git a/PKHeX.Core/Saves/SAV1Stadium.cs b/PKHeX.Core/Saves/SAV1Stadium.cs new file mode 100644 index 000000000..0b27f85dd --- /dev/null +++ b/PKHeX.Core/Saves/SAV1Stadium.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; + +namespace PKHeX.Core +{ + public sealed class SAV1Stadium : SaveFile, ILangDeviantSave + { + protected override string BAKText => $"{OT} ({Version}) - {PlayTimeString}"; + public override string Filter => "SAV File|*.sav|All Files|*.*"; + public override string Extension => ".sav"; + public bool Japanese { get; } + public bool Korean => false; + 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.Stadium; + + public override SaveFile Clone() => new SAV1Stadium((byte[])Data.Clone(), Japanese); + + 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, 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 ? 5 : 10; + public override int OTLength => StringLength; + public override int NickLength => StringLength; + public override int BoxCount => 84; + public override int BoxSlotCount => 6; + + 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, Japanese) {otname = ot, nick = nick}; + } + + protected override byte[] DecryptPKM(byte[] data) => data; + + public override PKM BlankPKM => new PK1(Japanese); + protected override int SIZE_STORED => Japanese ? 0x2D : 0x37; + protected override int SIZE_PARTY => Japanese ? 0x2D : 0x37; + + public SAV1Stadium(byte[] data) : this(data, IsStadiumJ(data)) { } + + public SAV1Stadium(byte[] data, bool japanese) : base(data) + { + Japanese = japanese; + Box = 0; + } + + public SAV1Stadium(bool japanese = false) : base(SaveUtil.SIZE_G1STAD) + { + Japanese = japanese; + Box = 0; + ClearBoxes(); + } + + private int TeamSize => Japanese ? TeamSizeJ : TeamSizeU; + private const int TeamSizeU = 0x160; + private const int TeamSizeJ = 0x128; + public override int GetBoxOffset(int box) => (box * TeamSize) + 0x10; + public override string GetBoxName(int box) => $"Team {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 IsStadiumU(byte[] data) + { + if (data.Length != SaveUtil.SIZE_G1STAD) + return true; + + for (int i = 0; i < 10; i++) + { + if (BitConverter.ToUInt32(data, 0x15A + (i * SAV1Stadium.TeamSizeU)) != 0x454B4F50) // POKE + return false; + } + return true; + } + + public static bool IsStadiumJ(byte[] data) + { + if (data.Length != SaveUtil.SIZE_G1STAD) + return true; + + for (int i = 0; i < 10; i++) + { + if (BitConverter.ToUInt32(data, 0x122 + (i * SAV1Stadium.TeamSizeJ)) != 0x454B4F50) // POKE + return false; + } + return true; + } + } +} diff --git a/PKHeX.Core/Saves/Util/SaveUtil.cs b/PKHeX.Core/Saves/Util/SaveUtil.cs index e14b376ac..524aba0bc 100644 --- a/PKHeX.Core/Saves/Util/SaveUtil.cs +++ b/PKHeX.Core/Saves/Util/SaveUtil.cs @@ -47,6 +47,7 @@ public static class SaveUtil public const int SIZE_G2VC_J = 0x10010; public const int SIZE_G2BAT_J = 0x1002C; public const int SIZE_G2EMU_J = 0x10030; + public const int SIZE_G1STAD = 0x20000; // same as G3RAW_U public const int SIZE_G1RAW = 0x8000; public const int SIZE_G1BAT = 0x802C; @@ -120,6 +121,8 @@ private static GameVersion GetSAVType(byte[] data) return Gen3; if (GetIsRanch4(data)) // ranch return DPPt; + if (SAV1Stadium.IsStadiumU(data) || SAV1Stadium.IsStadiumJ(data)) + return Stadium; if ((ver = GetIsG8SAV(data)) != Invalid) return ver; @@ -524,6 +527,7 @@ private static GameVersion GetIsG8SAV(byte[] data) case DPPt: return new SAV4Ranch(data); case Gen4: return new Bank4(data); case Gen7: return Bank7.GetBank7(data); + case Stadium: return new SAV1Stadium(data); // No pattern matched default: return null;