From ec24b8b8d50db07566b49133895aa9a2f9022ca6 Mon Sep 17 00:00:00 2001 From: Kurt Date: Sun, 6 Oct 2024 01:10:32 -0500 Subject: [PATCH] Enhance Gen5 download content I/O Initially just C-Gear, can do better! Adds i/o for Pokedex skins, Musicals, Battle Videos, Movies & PWT (B2W2), and a research-only tab for Battle Test (never distributed by GameFreak, so doesn't work). --- PKHeX.Core/PKM/Strings/StringConverter5.cs | 2 +- PKHeX.Core/Saves/Access/ISaveBlock5BW.cs | 2 + .../Saves/Access/SaveBlockAccessor5B2W2.cs | 7 +- .../Saves/Access/SaveBlockAccessor5BW.cs | 7 +- PKHeX.Core/Saves/SAV5.cs | 185 +++-- PKHeX.Core/Saves/SAV5B2W2.cs | 70 +- PKHeX.Core/Saves/SAV5BW.cs | 22 +- .../Battle Videos/BattleVideo5.cs | 185 +++-- .../Saves/Substructures/Gen5/BattleTest5.cs | 12 + .../Saves/Substructures/Gen5/Musical5.cs | 20 +- .../Saves/Substructures/Gen5/MusicalShow5.cs | 21 + .../Saves/Substructures/Gen5/PlayerData5.cs | 145 +++- .../Substructures/Gen5/PlayerPosition5.cs | 12 + .../Substructures/Gen5/PokestarMovie5.cs | 14 + .../Saves/Substructures/Gen5/SkinInfo5.cs | 48 ++ .../Gen5/TiledImage/PokeDexSkin5.cs | 30 + .../Saves/Substructures/Gen5/UnityTower5.cs | 2 +- .../Substructures/Gen5/WorldTournament5.cs | 48 ++ .../Controls/SAV Editor/SAVEditor.cs | 2 +- .../Save Editors/Gen5/SAV_DLC5.Designer.cs | 635 ++++++++++++++++++ .../Subforms/Save Editors/Gen5/SAV_DLC5.cs | 459 +++++++++++++ 21 files changed, 1797 insertions(+), 131 deletions(-) create mode 100644 PKHeX.Core/Saves/Substructures/Gen5/BattleTest5.cs create mode 100644 PKHeX.Core/Saves/Substructures/Gen5/MusicalShow5.cs create mode 100644 PKHeX.Core/Saves/Substructures/Gen5/PlayerPosition5.cs create mode 100644 PKHeX.Core/Saves/Substructures/Gen5/PokestarMovie5.cs create mode 100644 PKHeX.Core/Saves/Substructures/Gen5/SkinInfo5.cs create mode 100644 PKHeX.Core/Saves/Substructures/Gen5/TiledImage/PokeDexSkin5.cs create mode 100644 PKHeX.Core/Saves/Substructures/Gen5/WorldTournament5.cs create mode 100644 PKHeX.WinForms/Subforms/Save Editors/Gen5/SAV_DLC5.Designer.cs create mode 100644 PKHeX.WinForms/Subforms/Save Editors/Gen5/SAV_DLC5.cs diff --git a/PKHeX.Core/PKM/Strings/StringConverter5.cs b/PKHeX.Core/PKM/Strings/StringConverter5.cs index ca3cabb5f..7289770ad 100644 --- a/PKHeX.Core/PKM/Strings/StringConverter5.cs +++ b/PKHeX.Core/PKM/Strings/StringConverter5.cs @@ -31,7 +31,7 @@ public static int LoadString(ReadOnlySpan data, Span result) for (; i < data.Length; i += 2) { var value = ReadUInt16LittleEndian(data[i..]); - if (value == Terminator) + if (value is Terminator or 0) break; result[ctr++] = StringConverter4Util.NormalizeGenderSymbol((char)value); } diff --git a/PKHeX.Core/Saves/Access/ISaveBlock5BW.cs b/PKHeX.Core/Saves/Access/ISaveBlock5BW.cs index 25b1e0b74..b5fafbe97 100644 --- a/PKHeX.Core/Saves/Access/ISaveBlock5BW.cs +++ b/PKHeX.Core/Saves/Access/ISaveBlock5BW.cs @@ -13,6 +13,8 @@ public interface ISaveBlock5BW Daycare5 Daycare { get; } BoxLayout5 BoxLayout { get; } PlayerData5 PlayerData { get; } + UnityTower5 UnityTower { get; } + SkinInfo5 SkinInfo { get; } BattleSubwayPlay5 BattleSubwayPlay { get; } BattleSubway5 BattleSubway { get; } Entralink5 Entralink { get; } diff --git a/PKHeX.Core/Saves/Access/SaveBlockAccessor5B2W2.cs b/PKHeX.Core/Saves/Access/SaveBlockAccessor5B2W2.cs index 144ab675f..af69b3771 100644 --- a/PKHeX.Core/Saves/Access/SaveBlockAccessor5B2W2.cs +++ b/PKHeX.Core/Saves/Access/SaveBlockAccessor5B2W2.cs @@ -74,7 +74,7 @@ public sealed class SaveBlockAccessor5B2W2(SAV5B2W2 sav) new(0x22A00, 0x0850, 0x23252, 0x25F78), // 60 Entralink Forest pokémon data new(0x23300, 0x0284, 0x23586, 0x25F7A), // 61 Answered Questions new(0x23600, 0x0010, 0x23612, 0x25F7C), // 62 Unity Tower - new(0x23700, 0x00a8, 0x237AA, 0x25F7E), // 63 PWT related data + new(0x23700, 0x00a8, 0x237AA, 0x25F7E), // 63 Battle Institute & PWT related data new(0x23800, 0x016c, 0x2396E, 0x25F80), // 64 ??? new(0x23A00, 0x0080, 0x23A82, 0x25F82), // 65 ??? new(0x23B00, 0x00fc, 0x23BFE, 0x25F84), // 66 Hollow/Rival Block @@ -90,9 +90,10 @@ public sealed class SaveBlockAccessor5B2W2(SAV5B2W2 sav) public IReadOnlyList BlockInfo => BlocksB2W2; public BoxLayout5 BoxLayout { get; } = new(sav, Block(sav, 0)); public MyItem5B2W2 Items { get; } = new(sav, Block(sav, 25)); - public PlayerData5 PlayerData { get; } = new(sav, Block(sav, 27)); + public PlayerData5B2W2 PlayerData { get; } = new(sav, Block(sav, 27)); public PlayerPosition5 PlayerPosition { get; } = new(sav, Block(sav, 28)); public UnityTower5 UnityTower { get; } = new(sav, Block(sav, 29)); + public SkinInfo5B2W2 SkinInfo { get; } = new(sav, Block(sav, 32)); public MysteryBlock5 Mystery { get; } = new(sav, Block(sav, 34)); public GlobalLink5 GlobalLink { get; } = new(sav, Block(sav, 35)); public Chatter5 Chatter { get; } = new(sav, Block(sav, 36)); @@ -120,6 +121,8 @@ public sealed class SaveBlockAccessor5B2W2(SAV5B2W2 sav) MyItem ISaveBlock5BW.Items => Items; Entralink5 ISaveBlock5BW.Entralink => Entralink; Misc5 ISaveBlock5BW.Misc => Misc; + SkinInfo5 ISaveBlock5BW.SkinInfo => SkinInfo; + PlayerData5 ISaveBlock5BW.PlayerData => PlayerData; public static Memory Block(SAV5B2W2 sav, int index) { diff --git a/PKHeX.Core/Saves/Access/SaveBlockAccessor5BW.cs b/PKHeX.Core/Saves/Access/SaveBlockAccessor5BW.cs index f06ff3413..3a0b3b4d2 100644 --- a/PKHeX.Core/Saves/Access/SaveBlockAccessor5BW.cs +++ b/PKHeX.Core/Saves/Access/SaveBlockAccessor5BW.cs @@ -75,7 +75,7 @@ public sealed class SaveBlockAccessor5BW(SAV5BW sav) : ISaveBlockAccessor BlockInfo => BlocksBW; public BoxLayout5 BoxLayout { get; } = new(sav, Block(sav, 0)); public MyItem5BW Items { get; } = new(sav, Block(sav, 25)); - public PlayerData5 PlayerData { get; } = new(sav, Block(sav, 27)); + public PlayerData5BW PlayerData { get; } = new(sav, Block(sav, 27)); public PlayerPosition5 PlayerPosition { get; } = new(sav, Block(sav, 28)); public UnityTower5 UnityTower { get; } = new(sav, Block(sav, 29)); + public SkinInfo5BW SkinInfo { get; } = new(sav, Block(sav, 32)); public MysteryBlock5 Mystery { get; } = new(sav, Block(sav, 34)); public GlobalLink5 GlobalLink { get; } = new(sav, Block(sav, 35)); public Chatter5 Chatter { get; } = new(sav, Block(sav, 36)); @@ -111,6 +112,8 @@ public sealed class SaveBlockAccessor5BW(SAV5BW sav) : ISaveBlockAccessor Items; Entralink5 ISaveBlock5BW.Entralink => Entralink; Misc5 ISaveBlock5BW.Misc => Misc; + SkinInfo5 ISaveBlock5BW.SkinInfo => SkinInfo; + PlayerData5 ISaveBlock5BW.PlayerData => PlayerData; public static Memory Block(SAV5BW sav, int index) { diff --git a/PKHeX.Core/Saves/SAV5.cs b/PKHeX.Core/Saves/SAV5.cs index 5a200ae89..67e6ba9ec 100644 --- a/PKHeX.Core/Saves/SAV5.cs +++ b/PKHeX.Core/Saves/SAV5.cs @@ -57,8 +57,6 @@ private void Initialize() public override bool ChecksumsValid => AllBlocks.GetChecksumsValid(Data); public override string ChecksumInfo => AllBlocks.GetChecksumInfo(Data); - private int CGearInfoOffset => AllBlocks[32].Offset; // 0x1C000 - Options / Skin Info - protected abstract int CGearDataOffset { get; } // extdata public sealed override bool HasPokeDex => true; // Daycare @@ -125,44 +123,6 @@ public sealed override int LoadString(ReadOnlySpan data, Span result public sealed override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) => StringConverter5.SetString(destBuffer, value, maxLength, Language, option); - // DLC - private int CGearSkinInfoOffset => CGearInfoOffset + (this is SAV5B2W2 ? 0x10 : 0) + 0x24; - - private bool CGearSkinPresent - { - get => Data[CGearSkinInfoOffset + 2] == 1; - set => Data[CGearSkinInfoOffset + 2] = PlayerData.Data[this is SAV5B2W2 ? 0x6C : 0x54] = value ? (byte)1 : (byte)0; - } - - private static ReadOnlySpan DLCFooter => [ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x27, 0x00, 0x00, 0x27, 0x35, 0x05, 0x31, 0x00, 0x00 ]; - - public Memory CGearSkinData => Data.AsMemory(CGearDataOffset, CGearBackground.SIZE); - - public void SetCGearSkin(ReadOnlySpan value) - { - ArgumentOutOfRangeException.ThrowIfNotEqual(value.Length, CGearBackground.SIZE); - SetData(value, CGearDataOffset); - - ushort chk = Checksums.CRC16_CCITT(value); - var footer = Data.AsSpan(CGearDataOffset + value.Length); - - WriteUInt16LittleEndian(footer, 1); // block updated once - WriteUInt16LittleEndian(footer[2..], chk); // checksum - WriteUInt16LittleEndian(footer[0x100..], chk); // second checksum - - DLCFooter.CopyTo(footer[0x102..]); - - ushort skinchkval = Checksums.CRC16_CCITT(footer[0x100..0x104]); - WriteUInt16LittleEndian(footer[0x112..], skinchkval); - - // Indicate in the save file that data is present - WriteUInt16LittleEndian(Data.AsSpan(0x19438), 0xC21E); - - WriteUInt16LittleEndian(Data.AsSpan(CGearSkinInfoOffset), chk); - CGearSkinPresent = true; - State.Edited = true; - } - public abstract IReadOnlyList AllBlocks { get; } public abstract MyItem Items { get; } public abstract Zukan5 Zukan { get; } @@ -179,6 +139,7 @@ public void SetCGearSkin(ReadOnlySpan value) public abstract Musical5 Musical { get; } public abstract Encount5 Encount { get; } public abstract UnityTower5 UnityTower { get; } + public abstract SkinInfo5 SkinInfo { get; } public abstract EventWork5 EventWork { get; } public abstract BattleBox5 BattleBox { get; } public abstract EntreeForest EntreeForest { get; } @@ -189,11 +150,6 @@ public void SetCGearSkin(ReadOnlySpan value) public abstract Record5 Records { get; } IEventFlag37 IEventFlagProvider37.EventWork => EventWork; - public abstract Memory BattleVideoNative { get; } - public abstract Memory BattleVideoDownload1 { get; } - public abstract Memory BattleVideoDownload2 { get; } - public abstract Memory BattleVideoDownload3 { get; } - protected override byte[] GetFinalData() { EntreeForest.EndAccess(); @@ -212,4 +168,143 @@ public MailDetail GetMail(int mailIndex) } IMysteryGiftStorage IMysteryGiftStorageProvider.MysteryGiftStorage => Mystery; + protected abstract int ExtBattleVideoNativeOffset { get; } + protected abstract int ExtBattleVideoDownload1Offset { get; } + protected abstract int ExtBattleVideoDownload2Offset { get; } + protected abstract int ExtBattleVideoDownload3Offset { get; } + protected abstract int ExtCGearOffset { get; } + protected abstract int ExtBattleTestOffset { get; } + protected abstract int ExtMusicalDownloadOffset { get; } + protected abstract int ExtPokeDexSkinOffset { get; } + protected abstract int ExtHallOfFame1Offset { get; } + protected abstract int ExtHallOfFame2Offset { get; } + protected abstract int ExtLink1Offset { get; } + protected abstract int ExtLink2Offset { get; } + + /// Variable sized NARC download depending on the game (B/W vs B2/W2). + public abstract int MusicalDownloadSize { get; } + public const int HallOfFameSize = 0x155C; + private const int Link3DSDataSize = 0x400; + + public Memory BattleVideoNative => Data.AsMemory(ExtBattleVideoNativeOffset, BattleVideo5.SIZE); + public Memory BattleVideoDownload1 => Data.AsMemory(ExtBattleVideoDownload1Offset, BattleVideo5.SIZE); + public Memory BattleVideoDownload2 => Data.AsMemory(ExtBattleVideoDownload2Offset, BattleVideo5.SIZE); + public Memory BattleVideoDownload3 => Data.AsMemory(ExtBattleVideoDownload3Offset, BattleVideo5.SIZE); + public Memory CGearSkinData => Data.AsMemory(ExtCGearOffset, CGearBackground.SIZE); + public Memory BattleTest => Data.AsMemory(ExtBattleTestOffset, BattleTest5.SIZE); + public Memory MusicalDownloadData => Data.AsMemory(ExtMusicalDownloadOffset, MusicalDownloadSize); + public Memory PokedexSkinData => Data.AsMemory(ExtPokeDexSkinOffset, PokeDexSkin5.SIZE); + public Memory HallOfFame1 => Data.AsMemory(ExtHallOfFame1Offset, HallOfFameSize); + public Memory HallOfFame2 => Data.AsMemory(ExtHallOfFame2Offset, HallOfFameSize); + public Memory Link1Data => Data.AsMemory(ExtLink1Offset, Link3DSDataSize); + public Memory Link2Data => Data.AsMemory(ExtLink2Offset, Link3DSDataSize); + + private const int ExtFooterLength = 0x14; + + /// + /// Writes an extdata section + /// + /// Section of data to write + /// Offset within the save file to write to + /// Expected size of the data + /// Update count + /// Checksum of the + protected ushort WriteExtSection(ReadOnlySpan data, int offset, int size, ushort count) + { + ArgumentOutOfRangeException.ThrowIfNotEqual(data.Length, size); + SetData(data, offset); + + // Update Tail Section + ushort chk = Checksums.CRC16_CCITT(data); + var tail = Data.AsSpan(offset + size); + WriteUInt16LittleEndian(tail, count); // block updated counter + WriteUInt16LittleEndian(tail[2..], chk); // checksum + + // Update Footer + int lengthInner = size + 0x100 - (size % 0x100); // wasting 0x100 bytes, nice! + var footer = Data.AsSpan(offset + lengthInner, ExtFooterLength); + WriteFooterDLC(footer, chk, lengthInner + ExtFooterLength, count); + return chk; + } + + private static void WriteFooterDLC(Span data, ushort chk, int length, ushort count) + { + WriteInt32LittleEndian(data, chk); + chk = Checksums.CRC16_CCITT(data[..4]); // checksum of a checksum + WriteFooter51(data[4..], chk, length, count); + } + + private static void WriteFooter51(Span data, ushort chk, int length, ushort count) + { + WriteInt32LittleEndian(data, count); + WriteInt32LittleEndian(data[0x04..], length); + WriteInt32LittleEndian(data[0x08..], 0x31053527); // '5.1 magic prime 822424871 + + WriteUInt16LittleEndian(data[0x0C..], 0); + WriteUInt16LittleEndian(data[0x0E..], chk); + } + + public void SetBattleVideo(int index, ReadOnlySpan data, ushort count = 1) + { + PlayerData.UpdateExtData(ExtDataSectionNote5.BattleVideo0 + index, count); + var offset = index switch + { + 0 => ExtBattleVideoNativeOffset, + 1 => ExtBattleVideoDownload1Offset, + 2 => ExtBattleVideoDownload2Offset, + 3 => ExtBattleVideoDownload3Offset, + _ => throw new ArgumentOutOfRangeException(nameof(index)), + }; + WriteExtSection(data, offset, BattleVideo5.SIZE, count); + } + + public Memory GetBattleVideo(int index) => index switch + { + 0 => BattleVideoNative, + 1 => BattleVideoDownload1, + 2 => BattleVideoDownload2, + 3 => BattleVideoDownload3, + _ => throw new ArgumentOutOfRangeException(nameof(index)), + }; + + public void SetCGearSkin(ReadOnlySpan data, ushort count = 1) + { + var chk = WriteExtSection(data, ExtCGearOffset, CGearBackground.SIZE, count); + + // Indicate in the save file that data is present + SkinInfo.CGearSkinChecksum = chk; + SkinInfo.HasCGearSkin = true; + PlayerData.UpdateExtData(ExtDataSectionNote5.CGearSkin, count); + } + + public void SetBattleTest(ReadOnlySpan data, ushort count = 1) + { + WriteExtSection(data, ExtBattleTestOffset, BattleTest5.SIZE, count); + PlayerData.UpdateExtData(ExtDataSectionNote5.BattleTest, count); + } + + public void SetMusical(ReadOnlySpan data, ushort count = 1) + { + WriteExtSection(data, ExtMusicalDownloadOffset, MusicalDownloadSize, count); + PlayerData.UpdateExtData(ExtDataSectionNote5.Musical, count); + Musical.IsAvailableMusicalDLC = true; + } + + public void SetPokeDexSkin(ReadOnlySpan data, ushort count = 1) + { + WriteExtSection(data, ExtPokeDexSkinOffset, PokeDexSkin5.SIZE, count); + PlayerData.UpdateExtData(ExtDataSectionNote5.PokedexSkin, count); + } + + public void SetHallOfFame(ReadOnlySpan data, ushort count = 1) + { + WriteExtSection(data, ExtHallOfFame1Offset, HallOfFameSize, count); + PlayerData.UpdateExtData(ExtDataSectionNote5.HallOfFame, count); + } + + public void SetLink1Data(ReadOnlySpan data, ushort count = 1) + => WriteExtSection(data, ExtLink1Offset, Link3DSDataSize, count); + + public void SetLink2Data(ReadOnlySpan data, ushort count = 1) + => WriteExtSection(data, ExtLink2Offset, Link3DSDataSize, count); } diff --git a/PKHeX.Core/Saves/SAV5B2W2.cs b/PKHeX.Core/Saves/SAV5B2W2.cs index bf16c376b..c37ef0a83 100644 --- a/PKHeX.Core/Saves/SAV5B2W2.cs +++ b/PKHeX.Core/Saves/SAV5B2W2.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; namespace PKHeX.Core; @@ -25,7 +26,7 @@ public sealed class SAV5B2W2 : SAV5, ISaveBlock5B2W2 public override Chatter5 Chatter => Blocks.Chatter; public override Daycare5 Daycare => Blocks.Daycare; public override BoxLayout5 BoxLayout => Blocks.BoxLayout; - public override PlayerData5 PlayerData => Blocks.PlayerData; + public override PlayerData5B2W2 PlayerData => Blocks.PlayerData; public override PlayerPosition5 PlayerPosition => Blocks.PlayerPosition; public override BattleSubwayPlay5 BattleSubwayPlay => Blocks.BattleSubwayPlay; public override BattleSubway5 BattleSubway => Blocks.BattleSubway; @@ -33,6 +34,7 @@ public sealed class SAV5B2W2 : SAV5, ISaveBlock5B2W2 public override Musical5 Musical => Blocks.Musical; public override Encount5 Encount => Blocks.Encount; public override UnityTower5 UnityTower => Blocks.UnityTower; + public override SkinInfo5 SkinInfo => Blocks.SkinInfo; public override EventWork5B2W2 EventWork => Blocks.EventWork; public override BattleBox5 BattleBox => Blocks.BattleBox; public override EntreeForest EntreeForest => Blocks.EntreeForest; @@ -59,9 +61,65 @@ public Span RivalTrash set { if (value.Length == MaxStringLengthTrainer * 2) value.CopyTo(Data.AsSpan(0x23BA4)); } } - public override Memory BattleVideoNative => Data.AsMemory(0x4C000, BattleVideo5.SIZE_USED); - public override Memory BattleVideoDownload1 => Data.AsMemory(0x4DA00, BattleVideo5.SIZE_USED); - public override Memory BattleVideoDownload2 => Data.AsMemory(0x4F400, BattleVideo5.SIZE_USED); - public override Memory BattleVideoDownload3 => Data.AsMemory(0x50E00, BattleVideo5.SIZE_USED); - protected override int CGearDataOffset => 0x52800; // ^ + 0x1A00 spacing + protected override int ExtBattleVideoNativeOffset => 0x4C000; // 0x1A00 + protected override int ExtBattleVideoDownload1Offset => 0x4DA00; // 0x1A00 + protected override int ExtBattleVideoDownload2Offset => 0x4F400; // 0x1A00 + protected override int ExtBattleVideoDownload3Offset => 0x50E00; // 0x1A00 + protected override int ExtCGearOffset => 0x52800; // 0x2800 + protected override int ExtBattleTestOffset => 0x55000; // 0x800 + protected override int ExtMusicalDownloadOffset => 0x55800; // 0x18000 + protected override int ExtPokeDexSkinOffset => 0x6D800; // 0x6800 + protected override int ExtHallOfFame1Offset => 0x74000; // 0x1800 + protected override int ExtHallOfFame2Offset => 0x75800; // 0x1800 + + public const int PokestarOffset = 0x77000; // 0x600 * 8 + public const int PWTOffset = 0x7A000; // 0x1400 * 3 + + public const int DreamRadarOffset = 0x7DC00; // 0x400, size 0xC8 ? + + protected override int ExtLink1Offset => 0x7E000; // 0x400 + protected override int ExtLink2Offset => 0x7E400; // 0x400 + + public override int MusicalDownloadSize => MusicalShow5.SIZE_B2W2; + public const int ExtUnk7E800 = 0x7E800; // Key System? + public const int ExtUnkCRGF = 0x7F000; // [0..F] => checksum @ 0x10 + + // B2/W2 have Pokéstar Studios and PWT DLC data too. + public const int PokestarCount = 8; + public const int PokestarLength = PokestarMovie5.SIZE; + public const int PokestarInterval = 0x600; + private static int GetPokestarOffset(int index) => PokestarOffset + (index * PokestarInterval); + + public Memory GetPokestarMovie([Range(0, PokestarCount)] int index) + { + ArgumentOutOfRangeException.ThrowIfGreaterThan(index, PokestarCount, nameof(index)); + return Data.AsMemory(GetPokestarOffset(index), PokestarLength); + } + + public void SetPokestarMovie([Range(0, PokestarCount)] int index, ReadOnlySpan data, ushort count = 1) + { + ArgumentOutOfRangeException.ThrowIfGreaterThan(index, PokestarCount, nameof(index)); + var offset = GetPokestarOffset(index); + WriteExtSection(data, offset, PokestarLength, count); + PlayerData.UpdateExtData(ExtDataSectionNote5.Movie1 + index, count); + } + + public const int PWTCount = 3; + public const int PWTLength = 0x1214; + public const int PWTInterval = 0x1400; + private static int GetPWTOffset(int index) => PWTOffset + (index * PWTInterval); + + public Memory GetPWT([Range(0, PWTCount)] int index) + { + ArgumentOutOfRangeException.ThrowIfGreaterThan(index, PWTCount, nameof(index)); + return Data.AsMemory(PWTOffset + (index * PWTInterval), PWTLength); + } + + public void SetPWT([Range(0, PWTCount)] int index, ReadOnlySpan data, ushort count = 1) + { + ArgumentOutOfRangeException.ThrowIfGreaterThan(index, PWTCount, nameof(index)); + var offset = GetPWTOffset(index); + WriteExtSection(data, offset, PWTLength, count); + PlayerData.UpdateExtData(ExtDataSectionNote5.PWT1 + index, count); + } } diff --git a/PKHeX.Core/Saves/SAV5BW.cs b/PKHeX.Core/Saves/SAV5BW.cs index 2bfea214c..c97b91352 100644 --- a/PKHeX.Core/Saves/SAV5BW.cs +++ b/PKHeX.Core/Saves/SAV5BW.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; namespace PKHeX.Core; @@ -33,6 +32,7 @@ public sealed class SAV5BW : SAV5 public override Musical5 Musical => Blocks.Musical; public override Encount5BW Encount => Blocks.Encount; public override UnityTower5 UnityTower => Blocks.UnityTower; + public override SkinInfo5 SkinInfo => Blocks.SkinInfo; public override EventWork5BW EventWork => Blocks.EventWork; public override BattleBox5 BattleBox => Blocks.BattleBox; public override EntreeForest EntreeForest => Blocks.EntreeForest; @@ -42,9 +42,19 @@ public sealed class SAV5BW : SAV5 public override AdventureInfo5 AdventureInfo => Blocks.AdventureInfo; public override Record5 Records => Blocks.Records; - public override Memory BattleVideoNative => Data.AsMemory(0x4A000, BattleVideo5.SIZE_USED); - public override Memory BattleVideoDownload1 => Data.AsMemory(0x4C000, BattleVideo5.SIZE_USED); - public override Memory BattleVideoDownload2 => Data.AsMemory(0x4E000, BattleVideo5.SIZE_USED); - public override Memory BattleVideoDownload3 => Data.AsMemory(0x50000, BattleVideo5.SIZE_USED); - protected override int CGearDataOffset => 0x52000; // ^ + 0x2000 spacing + protected override int ExtBattleVideoNativeOffset => 0x4A000; + protected override int ExtBattleVideoDownload1Offset => 0x4C000; // 0x2000 + protected override int ExtBattleVideoDownload2Offset => 0x4E000; // 0x2000 + protected override int ExtBattleVideoDownload3Offset => 0x50000; // 0x2000 + protected override int ExtCGearOffset => 0x52000; // 0x3000 + protected override int ExtBattleTestOffset => 0x55000; // 0x1000 + protected override int ExtMusicalDownloadOffset => 0x56000; // 0x20000 + protected override int ExtPokeDexSkinOffset => 0x76000; // 0x6800 + protected override int ExtHallOfFame1Offset => 0x7C800; // 0x1800 + protected override int ExtHallOfFame2Offset => 0x7E000; // 0x1800 + + protected override int ExtLink1Offset => 0x7F800; // 0x400 + protected override int ExtLink2Offset => 0x7FC00; // 0x400 + + public override int MusicalDownloadSize => MusicalShow5.SIZE_BW; } diff --git a/PKHeX.Core/Saves/Substructures/Battle Videos/BattleVideo5.cs b/PKHeX.Core/Saves/Substructures/Battle Videos/BattleVideo5.cs index 578f2fd4a..b6a3796b5 100644 --- a/PKHeX.Core/Saves/Substructures/Battle Videos/BattleVideo5.cs +++ b/PKHeX.Core/Saves/Substructures/Battle Videos/BattleVideo5.cs @@ -11,25 +11,21 @@ namespace PKHeX.Core; /// Chunk of memory storing the structure. public sealed class BattleVideo5(Memory Raw) : IBattleVideo { - private const int SIZE = 0x1900; - private const int SIZE_FOOTER = 0x14; - public const int SIZE_USED = SIZE + SIZE_FOOTER; - //public const int SIZE_BLOCK = 0x2000; - //bw is 0x2000 between each video, b2w2 is 0x1A00 + public const int SIZE = 0x18A4; + public const string Extension = "bv5"; - private Span Data => Raw.Span[..SIZE_USED]; + private Span Data => Raw.Span; + public bool IsUninitialized => !Data.ContainsAnyExcept(0xFF, 0); - public bool IsDecrypted; + public bool IsDecrypted { get; set; } private Span CryptoData => Data[0xC4..0x18A0]; - private Span Footer => Data.Slice(SIZE, SIZE_FOOTER); public Span DecryptedChecksumRegion => CryptoData; // ccitt16 -> 0x18A0 - public Span EncryptedChecksumRegion => Data[..0x18A4]; // ccitt16 -> 0x18A6 - public Span FooterChecksumRegion => Footer[..4]; // ccitt16 -> 0x12 public byte Generation => 5; public IEnumerable Contents => GetTeam(0).Concat(GetTeam(1)); // don't bother with multi-battles - public static bool IsValid(ReadOnlySpan data) => data.Length == SIZE_USED && ReadUInt32LittleEndian(data[0x1908..]) == SIZE_USED; + public static bool IsValid(ReadOnlySpan data) => data.Length == SIZE + && ReadUInt32LittleEndian(data[^2..]) == Checksums.CRC16_CCITT(data[..^4]); // Structure: // 0xC4 - 0x18A0: encrypted region @@ -42,26 +38,88 @@ public sealed class BattleVideo5(Memory Raw) : IBattleVideo // 0x0FA0: u16 count Max, u16 count Present, (6 * sizeof(0x70)) pokemon -- 0x2A4 // 0x1244: u16 count Max, u16 count Present, (6 * sizeof(0x70)) pokemon -- 0x2A4 // 0x14E8: u16 count Max, u16 count Present, (6 * sizeof(0x70)) pokemon -- 0x2A4 + + // Assuming participant are "real" humans, and not NPCs: // 0x178C: 0x44 trainer data (u32 flag, char[8] OT, remainder idc) // 0x17D0: 0x44 trainer data (u32 flag, char[8] OT, remainder idc) // 0x1814: 0x44 trainer data (u32 flag, char[8] OT, remainder idc) // 0x1858: 0x44 trainer data (u32 flag, char[8] OT, remainder idc) + // If a participant is an NPC, the flag is `2` and Trainer name is +0x10 from where it normally is. + // 0x189C: 0xC bytes ??? public const int SizeVideoPoke = 0x70; - public uint Key { get => ReadUInt32LittleEndian(Data); set => WriteUInt32LittleEndian(Data, value); } + public string VideoName + { + get => StringConverter5.GetString(Data[..0x10]); + set => StringConverter5.SetString(Data[..0x10], value, 8, 0, StringConverterOption.ClearZeroSafeTerminate); + } + + // Other Data? + + public byte TrainerBirthMonth { get => Data[0x15]; set => Data[0x15] = value; } + public byte TrainerAvatar { get => Data[0x16]; set => Data[0x16] = value; } + public byte TrainerNation { get => Data[0x17]; set => Data[0x17] = value; } + public byte TrainerRegion { get => Data[0x18]; set => Data[0x18] = value; } + public ushort TrainerSpecies { get => ReadUInt16LittleEndian(Data[0x1C..]); set => WriteUInt16LittleEndian(Data[0x1C..], value); } + + public ushort Team1Species1 { get => ReadUInt16LittleEndian(Data[0x80..]); set => WriteUInt16LittleEndian(Data[0x80..], value); } + public ushort Team1Species2 { get => ReadUInt16LittleEndian(Data[0x82..]); set => WriteUInt16LittleEndian(Data[0x82..], value); } + public ushort Team1Species3 { get => ReadUInt16LittleEndian(Data[0x84..]); set => WriteUInt16LittleEndian(Data[0x84..], value); } + public ushort Team1Species4 { get => ReadUInt16LittleEndian(Data[0x86..]); set => WriteUInt16LittleEndian(Data[0x86..], value); } + public ushort Team1Species5 { get => ReadUInt16LittleEndian(Data[0x88..]); set => WriteUInt16LittleEndian(Data[0x88..], value); } + public ushort Team1Species6 { get => ReadUInt16LittleEndian(Data[0x8A..]); set => WriteUInt16LittleEndian(Data[0x8A..], value); } + + public ushort Team2Species1 { get => ReadUInt16LittleEndian(Data[0x8C..]); set => WriteUInt16LittleEndian(Data[0x8C..], value); } + public ushort Team2Species2 { get => ReadUInt16LittleEndian(Data[0x8E..]); set => WriteUInt16LittleEndian(Data[0x8E..], value); } + public ushort Team2Species3 { get => ReadUInt16LittleEndian(Data[0x90..]); set => WriteUInt16LittleEndian(Data[0x90..], value); } + public ushort Team2Species4 { get => ReadUInt16LittleEndian(Data[0x92..]); set => WriteUInt16LittleEndian(Data[0x92..], value); } + public ushort Team2Species5 { get => ReadUInt16LittleEndian(Data[0x94..]); set => WriteUInt16LittleEndian(Data[0x94..], value); } + public ushort Team2Species6 { get => ReadUInt16LittleEndian(Data[0x96..]); set => WriteUInt16LittleEndian(Data[0x96..], value); } + + public ushort BattleNumber { get => ReadUInt16LittleEndian(Data[0xA4..]); set => WriteUInt16LittleEndian(Data[0xA4..], value); } + public byte BattleMode { get => Data[0xA6]; set => Data[0xA6] = value; } + public byte BattleType { get => Data[0xA7]; set => Data[0xA7] = value; } // Launcher + public ushort StartSentinel { get => ReadUInt16LittleEndian(Data[0xA8..]); set => WriteUInt16LittleEndian(Data[0xA8..], value); } + public byte Boolean { get => Data[0xAA]; set => Data[0xAA] = value; } // 0 or 1 + public byte OneHundred { get => Data[0xAB]; set => Data[0xAB] = value; } // 100 + + public ulong BattleVideoUploadId { get => ReadUInt64LittleEndian(Data[0xB8..]); set => WriteUInt64LittleEndian(Data[0xB8..], value); } + + // Encrypted Section + public ulong RandomSeed { get => ReadUInt64LittleEndian(Data[0xC4..]); set => WriteUInt64LittleEndian(Data[0xC4..], value); } + public ulong RandomMult { get => ReadUInt64LittleEndian(Data[0xCC..]); set => WriteUInt64LittleEndian(Data[0xCC..], value); } + public ulong RandomAdd { get => ReadUInt64LittleEndian(Data[0xD4..]); set => WriteUInt64LittleEndian(Data[0xD4..], value); } + public uint BattlePad1 { get => ReadUInt32LittleEndian(Data[0xDC..]); set => WriteUInt32LittleEndian(Data[0xDC..], value); } + public uint BattlePad2 { get => ReadUInt32LittleEndian(Data[0xE0..]); set => WriteUInt32LittleEndian(Data[0xE0..], value); } + + private const int TeamCount = 4; + private const int TeamLength = 0x2A4; + public Span Team1 => Data.Slice(0x0CFC, TeamLength); + public Span Team2 => Data.Slice(0x0FA0, TeamLength); + public Span Team3 => Data.Slice(0x1244, TeamLength); + public Span Team4 => Data.Slice(0x14E8, TeamLength); + + private const int TrainerLength = 0x44; + public Span Trainer1 => Data.Slice(0x178C, TrainerLength); // + 0x10 something else + public Span Trainer2 => Data.Slice(0x17D0, TrainerLength); + public Span Trainer3 => Data.Slice(0x1814, TrainerLength); + public Span Trainer4 => Data.Slice(0x1858, TrainerLength); + + // should always be 0xE281 + public ushort EndSentinel { get => ReadUInt16LittleEndian(Data[0x189C..]); set => WriteUInt16LittleEndian(Data[0x189C..], value); } + public ushort Unused { get => ReadUInt16LittleEndian(Data[0x189E..]); set => WriteUInt16LittleEndian(Data[0x189E..], value); } /// public ushort Seed { get => ReadUInt16LittleEndian(Data[0x18A0..]); set => WriteUInt16LittleEndian(Data[0x18A0..], value); } // 0000: ^ seed is truncated to u16. - public ushort EncryptedCount { get => ReadUInt16LittleEndian(Data[0x18A4..]); set => WriteUInt16LittleEndian(Data[0x18A4..], value); } - public ushort EncryptedChecksum { get => ReadUInt16LittleEndian(Data[0x18A6..]); set => WriteUInt16LittleEndian(Data[0x18A6..], value); } - // 0xFF padding(?) to 0x1900 + /// Decrypts the object. If already decrypted, does nothing. public void Decrypt() => SetDecryptedState(true); + /// Encrypts the object. If already encrypted, does nothing. public void Encrypt() => SetDecryptedState(false); - public void SetDecryptedState(bool state) + private void SetDecryptedState(bool state) { if (state == IsDecrypted) return; @@ -72,32 +130,12 @@ public void SetDecryptedState(bool state) /// /// Same as Gen4! /// - /// public uint GetEncryptionSeed() { uint seed = Seed; return seed | (~seed << 16); } - #region Footer - public bool SizeValid => BlockSize == SIZE; - public bool ChecksumValid => FooterChecksum == CalculateFooterChecksum(); - - public ushort EncryptedChecksumCopy { get => ReadUInt16LittleEndian(Footer); set => WriteUInt16LittleEndian(Footer, value); } - // 0000 - public uint FooterCount { get => ReadUInt32LittleEndian(Footer[0x4..]); set => WriteUInt32LittleEndian(Footer[0x4..], value); } - public int BlockSize { get => ReadInt32LittleEndian(Footer[0x8..]); set => WriteInt32LittleEndian(Footer[0x8..], value); } - public uint BlockID { get => ReadUInt32LittleEndian(Footer[0xC..]); set => WriteUInt32LittleEndian(Footer[0xC..], value); } - // 0000 - public ushort FooterChecksum { get => ReadUInt16LittleEndian(Footer[0x12..]); set => WriteUInt16LittleEndian(Footer[0x12..], value); } - - private ushort CalculateEncryptedChecksum() - { - if (IsDecrypted) - throw new InvalidOperationException("Cannot calculate checksum when decrypted."); - return Checksums.CRC16_CCITT(EncryptedChecksumRegion); - } - private ushort CalculateDecryptedChecksum() { if (!IsDecrypted) @@ -105,41 +143,21 @@ private ushort CalculateDecryptedChecksum() return Checksums.CRC16_CCITT(DecryptedChecksumRegion); } - private ushort CalculateFooterChecksum() => Checksums.CRC16_CCITT(FooterChecksumRegion); - public void RefreshChecksums() { var state = IsDecrypted; Decrypt(); Seed = CalculateDecryptedChecksum(); Encrypt(); - EncryptedChecksum = CalculateEncryptedChecksum(); - FooterChecksum = CalculateFooterChecksum(); SetDecryptedState(state); } - #endregion - #region Conversion - private const int TrainerCount = 4; - private const int TrainerLength = 0x2A4; - public Span Trainer1 => Data.Slice(0x0CFC, TrainerLength); - public Span Trainer2 => Data.Slice(0x0FA0, TrainerLength); - public Span Trainer3 => Data.Slice(0x1244, TrainerLength); - public Span Trainer4 => Data.Slice(0x14E8, TrainerLength); - public PK5[] GetTeam(int trainer) { - ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)trainer, TrainerCount); - var span = trainer switch - { - 0 => Trainer1, - 1 => Trainer2, - 2 => Trainer3, - 3 => Trainer4, - _ => throw new ArgumentOutOfRangeException(nameof(trainer)), - }; + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)trainer, TeamLength); + var span = GetTrainer(trainer); var state = IsDecrypted; Decrypt(); @@ -159,6 +177,30 @@ public PK5[] GetTeam(int trainer) return result; } + private Span GetTrainer(int trainer) => trainer switch + { + 0 => Trainer1, + 1 => Trainer2, + 2 => Trainer3, + 3 => Trainer4, + _ => throw new ArgumentOutOfRangeException(nameof(trainer)), + }; + + public string GetTrainerName(int trainer) + { + var state = IsDecrypted; + Decrypt(); + var data = GetTrainer(trainer); + uint flag = ReadUInt32LittleEndian(data); + data = data[4..]; + if (flag != 1) // not human? + data = data[0x10..]; + var buffer = data[..(7 * 2)]; + var result = !buffer.ContainsAnyExcept(0) ? string.Empty : StringConverter5.GetString(buffer); + SetDecryptedState(state); + return result; + } + /// public static void InflateToPK5(ReadOnlySpan video, Span entity) { @@ -235,4 +277,31 @@ private static void VerifySpanSizes(ReadOnlySpan entity, ReadOnlySpan data) + { + if (data.Length < SIZE) + return false; + if (ReadUInt64LittleEndian(data[0xCC..]) != BWRNG_M) // mul + return false; + if (ReadUInt64LittleEndian(data[0xD4..]) != BWRNG_A) // add + return false; + return true; + } + + public string GetTrainerNames() + { + var state = IsDecrypted; + var tr1 = GetTrainerName(0); + var tr2 = GetTrainerName(1); + var tr3 = GetTrainerName(2); + var tr4 = GetTrainerName(3); + SetDecryptedState(state); + if (string.IsNullOrWhiteSpace(tr3)) + return $"{tr1} vs {tr2}"; + return $"{tr1} & {tr2} vs {tr3} & {tr4}"; + } } diff --git a/PKHeX.Core/Saves/Substructures/Gen5/BattleTest5.cs b/PKHeX.Core/Saves/Substructures/Gen5/BattleTest5.cs new file mode 100644 index 000000000..7d346bc0b --- /dev/null +++ b/PKHeX.Core/Saves/Substructures/Gen5/BattleTest5.cs @@ -0,0 +1,12 @@ +using System; + +namespace PKHeX.Core; + +public class BattleTest5(Memory Raw) +{ + public const int SIZE = 0x5C8; + public const string Extension = "bt5"; + + private Span Data => Raw.Span; + public bool IsUninitialized => !Data.ContainsAnyExcept(0xFF, 0); +} diff --git a/PKHeX.Core/Saves/Substructures/Gen5/Musical5.cs b/PKHeX.Core/Saves/Substructures/Gen5/Musical5.cs index 1d1a9765e..eb92b2c93 100644 --- a/PKHeX.Core/Saves/Substructures/Gen5/Musical5.cs +++ b/PKHeX.Core/Saves/Substructures/Gen5/Musical5.cs @@ -4,8 +4,24 @@ namespace PKHeX.Core; public sealed class Musical5(SAV5 SAV, Memory raw) : SaveBlock(SAV, raw) { + public const int MusicalNameMaxLength = 20; + + private Span MusicalTrash => Data.Slice(0x208, MusicalNameMaxLength * 2); + + public string MusicalName + { + get => StringConverter5.GetString(MusicalTrash); + set => StringConverter5.SetString(MusicalTrash, value, MusicalNameMaxLength, 0, StringConverterOption.ClearZero); + } + private const int PropOffset = 0x258; + public bool IsAvailableMusicalDLC + { + get => Data[0x29E] == 1; + set => Data[0x29E] = value ? (byte)1 : (byte)0; + } + public void UnlockAllMusicalProps() { // 100 props, which is 12.5 bytes of bitflags. @@ -17,12 +33,12 @@ public void UnlockAllMusicalProps() public bool GetHasProp(int prop) { var bitOffset = prop >> 3; - return SAV.GetFlag(Data, PropOffset + bitOffset, prop & 7); + return FlagUtil.GetFlag(Data, PropOffset + bitOffset, prop & 7); } public void SetHasProp(int prop, bool value = true) { var bitOffset = prop >> 3; - SAV.SetFlag(Data, PropOffset + bitOffset, prop & 7, value); + FlagUtil.SetFlag(Data, PropOffset + bitOffset, prop & 7, value); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen5/MusicalShow5.cs b/PKHeX.Core/Saves/Substructures/Gen5/MusicalShow5.cs new file mode 100644 index 000000000..1a71efe18 --- /dev/null +++ b/PKHeX.Core/Saves/Substructures/Gen5/MusicalShow5.cs @@ -0,0 +1,21 @@ +using System; +using static System.Buffers.Binary.BinaryPrimitives; + +namespace PKHeX.Core; + +public sealed class MusicalShow5(Memory Raw) +{ + public const int SIZE_BW = 0x1FC00; // footers + align = 0x20000 + public const int SIZE_B2W2 = 0x17C00; // footers + align = 0x18000 + public const string Extension = "pms"; + + public Span Data => Raw.Span; + + public int Length + { + get => ReadInt32LittleEndian(Data); + set => WriteInt32LittleEndian(Data, value); + } + + public Memory NARC => Raw.Slice(0x10, Length); +} diff --git a/PKHeX.Core/Saves/Substructures/Gen5/PlayerData5.cs b/PKHeX.Core/Saves/Substructures/Gen5/PlayerData5.cs index 0d15b13f8..1304c664d 100644 --- a/PKHeX.Core/Saves/Substructures/Gen5/PlayerData5.cs +++ b/PKHeX.Core/Saves/Substructures/Gen5/PlayerData5.cs @@ -4,9 +4,9 @@ namespace PKHeX.Core; /// -/// Combined save block; 0x100 for first, 0x100 for second. +/// Player data as well as ExtData flagging. /// -public sealed class PlayerData5(SAV5 sav, Memory raw) : SaveBlock(sav, raw) +public abstract class PlayerData5(SAV5 sav, Memory raw) : SaveBlock(sav, raw) { private Span OriginalTrainerTrash => Data.Slice(4, 0x10); @@ -83,12 +83,143 @@ public int PlayedSeconds get => Data[0x24 + 3]; set => Data[0x24 + 3] = (byte)value; } + + // 0x25-0x2F ??? + + protected const ushort C21E = 0xC21E; + + public bool IsMagicBattleVideo0 { get => ReadUInt16LittleEndian(Data[0x30..]) == C21E; set => WriteUInt16LittleEndian(Data[0x30..], value ? C21E : (ushort)0); } + public bool IsMagicBattleVideo1 { get => ReadUInt16LittleEndian(Data[0x32..]) == C21E; set => WriteUInt16LittleEndian(Data[0x32..], value ? C21E : (ushort)0); } + public bool IsMagicBattleVideo2 { get => ReadUInt16LittleEndian(Data[0x34..]) == C21E; set => WriteUInt16LittleEndian(Data[0x34..], value ? C21E : (ushort)0); } + public bool IsMagicBattleVideo3 { get => ReadUInt16LittleEndian(Data[0x36..]) == C21E; set => WriteUInt16LittleEndian(Data[0x36..], value ? C21E : (ushort)0); } + public bool IsMagicCGearSkin { get => ReadUInt16LittleEndian(Data[0x38..]) == C21E; set => WriteUInt16LittleEndian(Data[0x38..], value ? C21E : (ushort)0); } + public bool IsMagicBattleTest { get => ReadUInt16LittleEndian(Data[0x3A..]) == C21E; set => WriteUInt16LittleEndian(Data[0x3A..], value ? C21E : (ushort)0); } + public bool IsMagicMusical { get => ReadUInt16LittleEndian(Data[0x3C..]) == C21E; set => WriteUInt16LittleEndian(Data[0x3C..], value ? C21E : (ushort)0); } + public bool IsMagicPokeDexSkin { get => ReadUInt16LittleEndian(Data[0x3E..]) == C21E; set => WriteUInt16LittleEndian(Data[0x3E..], value ? C21E : (ushort)0); } + public bool IsMagicHallOfFame { get => ReadUInt16LittleEndian(Data[0x40..]) == C21E; set => WriteUInt16LittleEndian(Data[0x40..], value ? C21E : (ushort)0); } + // 0x42 unused in B/W + + public abstract uint CountBattleVideo0 { get; set; } + public abstract uint CountBattleVideo1 { get; set; } + public abstract uint CountBattleVideo2 { get; set; } + public abstract uint CountBattleVideo3 { get; set; } + public abstract uint CountCGearSkin { get; set; } + public abstract uint CountBattleTest { get; set; } + public abstract uint CountMusical { get; set; } + public abstract uint CountPokedexSkin { get; set; } + public abstract uint CountHallOfFame { get; set; } + + public abstract void UpdateExtData(ExtDataSectionNote5 section, uint count); + public abstract (ushort, uint) GetExtData(ExtDataSectionNote5 section); } -public sealed class PlayerPosition5(SAV5 sav, Memory raw) : SaveBlock(sav, raw) +public sealed class PlayerData5BW(SAV5 sav, Memory raw) : PlayerData5(sav, raw) { - public int M { get => ReadInt32LittleEndian(Data[0x80..]); set => WriteUInt16LittleEndian(Data[0x80..], (ushort)value); } - public int X { get => ReadUInt16LittleEndian(Data[0x86..]); set => WriteUInt16LittleEndian(Data[0x86..], (ushort)value); } - public int Z { get => ReadUInt16LittleEndian(Data[0x8A..]); set => WriteUInt16LittleEndian(Data[0x8A..], (ushort)value); } - public int Y { get => ReadUInt16LittleEndian(Data[0x8E..]); set => WriteUInt16LittleEndian(Data[0x8E..], (ushort)value); } + public override uint CountBattleVideo0 { get => ReadUInt32LittleEndian(Data[0x44..]); set => WriteUInt32LittleEndian(Data[0x44..], value); } + public override uint CountBattleVideo1 { get => ReadUInt32LittleEndian(Data[0x48..]); set => WriteUInt32LittleEndian(Data[0x48..], value); } + public override uint CountBattleVideo2 { get => ReadUInt32LittleEndian(Data[0x4C..]); set => WriteUInt32LittleEndian(Data[0x4C..], value); } + public override uint CountBattleVideo3 { get => ReadUInt32LittleEndian(Data[0x50..]); set => WriteUInt32LittleEndian(Data[0x50..], value); } + public override uint CountCGearSkin { get => ReadUInt32LittleEndian(Data[0x54..]); set => WriteUInt32LittleEndian(Data[0x54..], value); } + public override uint CountBattleTest { get => ReadUInt32LittleEndian(Data[0x58..]); set => WriteUInt32LittleEndian(Data[0x58..], value); } + public override uint CountMusical { get => ReadUInt32LittleEndian(Data[0x5C..]); set => WriteUInt32LittleEndian(Data[0x5C..], value); } + public override uint CountPokedexSkin { get => ReadUInt32LittleEndian(Data[0x60..]); set => WriteUInt32LittleEndian(Data[0x60..], value); } + public override uint CountHallOfFame { get => ReadUInt32LittleEndian(Data[0x64..]); set => WriteUInt32LittleEndian(Data[0x64..], value); } + + public override void UpdateExtData(ExtDataSectionNote5 section, uint count) + { + ArgumentOutOfRangeException.ThrowIfGreaterThan((uint)section, (uint)ExtDataSectionNote5.HallOfFame, nameof(section)); + ushort value = count == 0 ? (ushort)0 : C21E; + WriteUInt16LittleEndian(Data[(0x30 + ((int)section * 2))..], value); + WriteUInt32LittleEndian(Data[(0x44 + ((int)section * 4))..], count); + } + + public override (ushort, uint) GetExtData(ExtDataSectionNote5 section) + { + ArgumentOutOfRangeException.ThrowIfGreaterThan((uint)section, (uint)ExtDataSectionNote5.HallOfFame, nameof(section)); + var state = ReadUInt16LittleEndian(Data[(0x30 + ((int)section * 2))..]); + var count = ReadUInt32LittleEndian(Data[(0x44 + ((int)section * 4))..]); + return (state, count); + } +} + +public sealed class PlayerData5B2W2(SAV5B2W2 sav, Memory raw) : PlayerData5(sav, raw) +{ + // Offsets are shifted due to the addition of new DLC blocks, which have the u16 and u32 counts. + public bool IsMagicMovie1 { get => ReadUInt16LittleEndian(Data[0x42..]) == C21E; set => WriteUInt16LittleEndian(Data[0x42..], value ? C21E : (ushort)0); } + public bool IsMagicMovie2 { get => ReadUInt16LittleEndian(Data[0x44..]) == C21E; set => WriteUInt16LittleEndian(Data[0x44..], value ? C21E : (ushort)0); } + public bool IsMagicMovie3 { get => ReadUInt16LittleEndian(Data[0x46..]) == C21E; set => WriteUInt16LittleEndian(Data[0x46..], value ? C21E : (ushort)0); } + public bool IsMagicMovie4 { get => ReadUInt16LittleEndian(Data[0x48..]) == C21E; set => WriteUInt16LittleEndian(Data[0x48..], value ? C21E : (ushort)0); } + public bool IsMagicMovie5 { get => ReadUInt16LittleEndian(Data[0x4A..]) == C21E; set => WriteUInt16LittleEndian(Data[0x4A..], value ? C21E : (ushort)0); } + public bool IsMagicMovie6 { get => ReadUInt16LittleEndian(Data[0x4C..]) == C21E; set => WriteUInt16LittleEndian(Data[0x4C..], value ? C21E : (ushort)0); } + public bool IsMagicMovie7 { get => ReadUInt16LittleEndian(Data[0x4E..]) == C21E; set => WriteUInt16LittleEndian(Data[0x4E..], value ? C21E : (ushort)0); } + public bool IsMagicMovie8 { get => ReadUInt16LittleEndian(Data[0x50..]) == C21E; set => WriteUInt16LittleEndian(Data[0x50..], value ? C21E : (ushort)0); } + public bool IsMagicPWT1 { get => ReadUInt16LittleEndian(Data[0x52..]) == C21E; set => WriteUInt16LittleEndian(Data[0x52..], value ? C21E : (ushort)0); } + public bool IsMagicPWT2 { get => ReadUInt16LittleEndian(Data[0x54..]) == C21E; set => WriteUInt16LittleEndian(Data[0x54..], value ? C21E : (ushort)0); } + public bool IsMagicPWT3 { get => ReadUInt16LittleEndian(Data[0x56..]) == C21E; set => WriteUInt16LittleEndian(Data[0x56..], value ? C21E : (ushort)0); } + public bool IsMagicUnused { get => ReadUInt16LittleEndian(Data[0x58..]) == C21E; set => WriteUInt16LittleEndian(Data[0x58..], value ? C21E : (ushort)0); } + + public override uint CountBattleVideo0 { get => ReadUInt32LittleEndian(Data[0x5C..]); set => WriteUInt32LittleEndian(Data[0x5C..], value); } + public override uint CountBattleVideo1 { get => ReadUInt32LittleEndian(Data[0x60..]); set => WriteUInt32LittleEndian(Data[0x60..], value); } + public override uint CountBattleVideo2 { get => ReadUInt32LittleEndian(Data[0x64..]); set => WriteUInt32LittleEndian(Data[0x64..], value); } + public override uint CountBattleVideo3 { get => ReadUInt32LittleEndian(Data[0x68..]); set => WriteUInt32LittleEndian(Data[0x68..], value); } + public override uint CountCGearSkin { get => ReadUInt32LittleEndian(Data[0x6C..]); set => WriteUInt32LittleEndian(Data[0x6C..], value); } + public override uint CountBattleTest { get => ReadUInt32LittleEndian(Data[0x70..]); set => WriteUInt32LittleEndian(Data[0x70..], value); } + public override uint CountMusical { get => ReadUInt32LittleEndian(Data[0x74..]); set => WriteUInt32LittleEndian(Data[0x74..], value); } + public override uint CountPokedexSkin { get => ReadUInt32LittleEndian(Data[0x78..]); set => WriteUInt32LittleEndian(Data[0x78..], value); } + public override uint CountHallOfFame { get => ReadUInt32LittleEndian(Data[0x7C..]); set => WriteUInt32LittleEndian(Data[0x7C..], value); } + public uint CountMovie1 { get => ReadUInt32LittleEndian(Data[0x80..]); set => WriteUInt32LittleEndian(Data[0x80..], value); } + public uint CountMovie2 { get => ReadUInt32LittleEndian(Data[0x84..]); set => WriteUInt32LittleEndian(Data[0x84..], value); } + public uint CountMovie3 { get => ReadUInt32LittleEndian(Data[0x88..]); set => WriteUInt32LittleEndian(Data[0x88..], value); } + public uint CountMovie4 { get => ReadUInt32LittleEndian(Data[0x8C..]); set => WriteUInt32LittleEndian(Data[0x8C..], value); } + public uint CountMovie5 { get => ReadUInt32LittleEndian(Data[0x90..]); set => WriteUInt32LittleEndian(Data[0x90..], value); } + public uint CountMovie6 { get => ReadUInt32LittleEndian(Data[0x94..]); set => WriteUInt32LittleEndian(Data[0x94..], value); } + public uint CountMovie7 { get => ReadUInt32LittleEndian(Data[0x98..]); set => WriteUInt32LittleEndian(Data[0x98..], value); } + public uint CountMovie8 { get => ReadUInt32LittleEndian(Data[0x9C..]); set => WriteUInt32LittleEndian(Data[0x9C..], value); } + public uint CountPWT1 { get => ReadUInt32LittleEndian(Data[0xA0..]); set => WriteUInt32LittleEndian(Data[0xA0..], value); } + public uint CountPWT2 { get => ReadUInt32LittleEndian(Data[0xA4..]); set => WriteUInt32LittleEndian(Data[0xA4..], value); } + public uint CountPWT3 { get => ReadUInt32LittleEndian(Data[0xA8..]); set => WriteUInt32LittleEndian(Data[0xA8..], value); } + public uint CountUnused { get => ReadUInt32LittleEndian(Data[0xAC..]); set => WriteUInt32LittleEndian(Data[0xAC..], value); } + + public override void UpdateExtData(ExtDataSectionNote5 section, uint count) + { + ArgumentOutOfRangeException.ThrowIfGreaterThan((uint)section, (uint)ExtDataSectionNote5.Unused, nameof(section)); + ushort value = count == 0 ? (ushort)0 : C21E; + WriteUInt16LittleEndian(Data[(0x30 + ((int)section * 2))..], value); + WriteUInt32LittleEndian(Data[(0x5C + ((int)section * 4))..], count); + } + + public override (ushort, uint) GetExtData(ExtDataSectionNote5 section) + { + ArgumentOutOfRangeException.ThrowIfGreaterThan((uint)section, (uint)ExtDataSectionNote5.HallOfFame, nameof(section)); + var state = ReadUInt16LittleEndian(Data[(0x30 + ((int)section * 2))..]); + var count = ReadUInt32LittleEndian(Data[(0x5C + ((int)section * 4))..]); + return (state, count); + } +} + +public enum ExtDataSectionNote5 +{ + BattleVideo0, + BattleVideo1, + BattleVideo2, + BattleVideo3, + CGearSkin, + BattleTest, + Musical, + PokedexSkin, + HallOfFame, + + // B2/W2 additions + Movie1, + Movie2, + Movie3, + Movie4, + Movie5, + Movie6, + Movie7, + Movie8, + PWT1, + PWT2, + PWT3, + Unused, } diff --git a/PKHeX.Core/Saves/Substructures/Gen5/PlayerPosition5.cs b/PKHeX.Core/Saves/Substructures/Gen5/PlayerPosition5.cs new file mode 100644 index 000000000..f7858a293 --- /dev/null +++ b/PKHeX.Core/Saves/Substructures/Gen5/PlayerPosition5.cs @@ -0,0 +1,12 @@ +using System; +using static System.Buffers.Binary.BinaryPrimitives; + +namespace PKHeX.Core; + +public sealed class PlayerPosition5(SAV5 sav, Memory raw) : SaveBlock(sav, raw) +{ + public int M { get => ReadInt32LittleEndian(Data[0x80..]); set => WriteUInt16LittleEndian(Data[0x80..], (ushort)value); } + public int X { get => ReadUInt16LittleEndian(Data[0x86..]); set => WriteUInt16LittleEndian(Data[0x86..], (ushort)value); } + public int Z { get => ReadUInt16LittleEndian(Data[0x8A..]); set => WriteUInt16LittleEndian(Data[0x8A..], (ushort)value); } + public int Y { get => ReadUInt16LittleEndian(Data[0x8E..]); set => WriteUInt16LittleEndian(Data[0x8E..], (ushort)value); } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen5/PokestarMovie5.cs b/PKHeX.Core/Saves/Substructures/Gen5/PokestarMovie5.cs new file mode 100644 index 000000000..2af66496d --- /dev/null +++ b/PKHeX.Core/Saves/Substructures/Gen5/PokestarMovie5.cs @@ -0,0 +1,14 @@ +using System; +using static System.Buffers.Binary.BinaryPrimitives; + +namespace PKHeX.Core; + +public class PokestarMovie5(Memory Raw) +{ + public const int SIZE = 0x4B4; + public const string Extension = "psm5"; + + private Span Data => Raw.Span; + public bool IsUninitialized => !Data.ContainsAnyExcept(0xFF, 0); + public string Name => IsUninitialized ? "Empty": "Movie"; +} diff --git a/PKHeX.Core/Saves/Substructures/Gen5/SkinInfo5.cs b/PKHeX.Core/Saves/Substructures/Gen5/SkinInfo5.cs new file mode 100644 index 000000000..f0b99f885 --- /dev/null +++ b/PKHeX.Core/Saves/Substructures/Gen5/SkinInfo5.cs @@ -0,0 +1,48 @@ +using System; +using static System.Buffers.Binary.BinaryPrimitives; + +namespace PKHeX.Core; + +public abstract class SkinInfo5(SAV5 SAV, Memory raw) : SaveBlock(SAV, raw) +{ + public abstract ushort CGearSkinChecksum { get; set; } + public abstract bool HasCGearSkin { get; set; } +} + +public sealed class SkinInfo5BW(SAV5BW SAV, Memory raw) : SkinInfo5(SAV, raw) +{ + public override ushort CGearSkinChecksum + { + get => ReadUInt16LittleEndian(Data[0x24..]); + set => WriteUInt16LittleEndian(Data[0x24..], value); + } + + public override bool HasCGearSkin + { + get => Data[0x26] == 1; + set => Data[0x26] = value ? (byte)1 : (byte)0; + } +} + +public sealed class SkinInfo5B2W2(SAV5B2W2 sav, Memory raw) : SkinInfo5(sav, raw) +{ + public override ushort CGearSkinChecksum + { + get => ReadUInt16LittleEndian(Data[0x34..]); + set => WriteUInt16LittleEndian(Data[0x34..], value); + } + + public override bool HasCGearSkin + { + get => Data[0x36] == 1; + set => Data[0x36] = value ? (byte)1 : (byte)0; + } + + public Span SayingTrash => Data.Slice(0x80, 0x12); + + public string Saying + { + get => sav.GetString(SayingTrash); + set => sav.SetString(SayingTrash, value, 8, StringConverterOption.ClearZeroSafeTerminate); + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen5/TiledImage/PokeDexSkin5.cs b/PKHeX.Core/Saves/Substructures/Gen5/TiledImage/PokeDexSkin5.cs new file mode 100644 index 000000000..423c88d03 --- /dev/null +++ b/PKHeX.Core/Saves/Substructures/Gen5/TiledImage/PokeDexSkin5.cs @@ -0,0 +1,30 @@ +using System; + +namespace PKHeX.Core; + +public sealed class PokeDexSkin5(Memory Raw) +{ + public const string Extension = "pds"; + + /// Pixel width of the background image. + public const int Width = 256; // px + /// Pixel height of the background image. + public const int Height = 192; // px + + public const int SIZE = LengthTilePool + LengthColorData + LengthColorDataBackground + Unused + 4; // 0x6204 + + private const int TilePoolCount = 768; // 0x300 + private const int CountColorForeground = 16; + private const int CountColorBackground = 64; + + private const int LengthTilePool = TilePoolCount * PaletteTile.SIZE; // 0x6000 + private const int LengthColorData = CountColorForeground * sizeof(ushort); // 0x20 + private const int LengthColorDataBackground = CountColorBackground * sizeof(ushort); // 0x80 + private const int Unused = 0x160; + + public Span Data => Raw.Span; + public Span ColorChoices => Data[..LengthTilePool]; + public Span ColorForeground => Data.Slice(LengthTilePool, LengthColorData); + public Span ColorBackground => Data.Slice(LengthTilePool + LengthColorData, LengthColorDataBackground); + public bool IsUninitialized => !Data.ContainsAnyExcept(0xFF, 0); +} diff --git a/PKHeX.Core/Saves/Substructures/Gen5/UnityTower5.cs b/PKHeX.Core/Saves/Substructures/Gen5/UnityTower5.cs index 331a7bce3..c9a42c978 100644 --- a/PKHeX.Core/Saves/Substructures/Gen5/UnityTower5.cs +++ b/PKHeX.Core/Saves/Substructures/Gen5/UnityTower5.cs @@ -55,7 +55,7 @@ public GeonetPoint GetCountrySubregion(byte country, byte subregion) { int index = GeonetOffset + ((country - 1) * 16) + (subregion / 4); int shift = 2 * (subregion % 4); - return (GeonetPoint)(((Data[index] & 0b11 << shift) >> shift)); + return (GeonetPoint)((Data[index] & 0b11 << shift) >> shift); } public void SetCountrySubregion(byte country, byte subregion, GeonetPoint point) diff --git a/PKHeX.Core/Saves/Substructures/Gen5/WorldTournament5.cs b/PKHeX.Core/Saves/Substructures/Gen5/WorldTournament5.cs new file mode 100644 index 000000000..9e6c6345d --- /dev/null +++ b/PKHeX.Core/Saves/Substructures/Gen5/WorldTournament5.cs @@ -0,0 +1,48 @@ +using System; +using static System.Buffers.Binary.BinaryPrimitives; + +namespace PKHeX.Core; + +public class WorldTournament5(Memory Raw) +{ + public const int SIZE = 0x1214; + public const string Extension = "pwt"; + + private Span Data => Raw.Span; + public bool IsUninitialized => !Data.ContainsAnyExcept(0xFF, 0); + + private Span NameTrash => Data[0xA..0x54]; // 36 chars + private Span DescriptionTrash => Data[0x54..0xEA]; // 74 chars + private Span AboutTrash => Data[0xEA..0x1C8]; // 110 chars + + public const int NameLength = 36; // chars + public const int DescriptionLength = 74; // chars + public const int AboutLength = 110; // chars + + public string Name + { + get => StringConverter5.GetString(NameTrash); + set => StringConverter5.SetString(NameTrash, value, NameLength, 0, StringConverterOption.ClearFF); + } + + public string Description + { + get => StringConverter5.GetString(DescriptionTrash); + set => StringConverter5.SetString(DescriptionTrash, value, DescriptionLength, 0, StringConverterOption.ClearFF); + } + + public string About + { + get => StringConverter5.GetString(AboutTrash); + set => StringConverter5.SetString(AboutTrash, value, AboutLength, 0, StringConverterOption.ClearFF); + } + + public ushort Checksum + { + get => ReadUInt16LittleEndian(Data[^4..]); + set => WriteUInt16LittleEndian(Data[^4..], value); + } + + private ushort CalculateChecksum() => Checksums.CRC16_CCITT(Data[..^4]); + public void RefreshChecksums() => Checksum = CalculateChecksum(); +} diff --git a/PKHeX.WinForms/Controls/SAV Editor/SAVEditor.cs b/PKHeX.WinForms/Controls/SAV Editor/SAVEditor.cs index b089bb3fa..94bf70cb2 100644 --- a/PKHeX.WinForms/Controls/SAV Editor/SAVEditor.cs +++ b/PKHeX.WinForms/Controls/SAV Editor/SAVEditor.cs @@ -609,7 +609,7 @@ private static void OpenDialog(Form f) private void B_CellsStickers_Click(object sender, EventArgs e) => OpenDialog(new SAV_ZygardeCell((SAV7)SAV)); private void B_LinkInfo_Click(object sender, EventArgs e) => OpenDialog(new SAV_Link6(SAV)); private void B_OpenApricorn_Click(object sender, EventArgs e) => OpenDialog(new SAV_Apricorn((SAV4HGSS)SAV)); - private void B_CGearSkin_Click(object sender, EventArgs e) => OpenDialog(new SAV_CGearSkin((SAV5)SAV)); + private void B_CGearSkin_Click(object sender, EventArgs e) => OpenDialog(new SAV_DLC5((SAV5)SAV)); private void B_OpenTrainerInfo_Click(object sender, EventArgs e) => OpenDialog(GetTrainerEditor(SAV)); private void B_OpenOPowers_Click(object sender, EventArgs e) => OpenDialog(new SAV_OPower((ISaveBlock6Main)SAV)); private void B_OpenHoneyTreeEditor_Click(object sender, EventArgs e) => OpenDialog(new SAV_HoneyTree((SAV4Sinnoh)SAV)); diff --git a/PKHeX.WinForms/Subforms/Save Editors/Gen5/SAV_DLC5.Designer.cs b/PKHeX.WinForms/Subforms/Save Editors/Gen5/SAV_DLC5.Designer.cs new file mode 100644 index 000000000..bc1d7456d --- /dev/null +++ b/PKHeX.WinForms/Subforms/Save Editors/Gen5/SAV_DLC5.Designer.cs @@ -0,0 +1,635 @@ +using PKHeX.WinForms.Controls; + +namespace PKHeX.WinForms +{ + partial class SAV_DLC5 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + PB_CGearBackground = new System.Windows.Forms.PictureBox(); + B_Cancel = new System.Windows.Forms.Button(); + B_Save = new System.Windows.Forms.Button(); + B_ImportPNG = new System.Windows.Forms.Button(); + B_ExportPNG = new System.Windows.Forms.Button(); + B_ExportCGB = new System.Windows.Forms.Button(); + B_ImportCGB = new System.Windows.Forms.Button(); + TC_Tabs = new VerticalTabControl(); + Tab_CGear = new System.Windows.Forms.TabPage(); + Tab_PokeDex = new System.Windows.Forms.TabPage(); + B_PokeDexBackgroundLoad = new System.Windows.Forms.Button(); + B_PokeDexBackgroundSave = new System.Windows.Forms.Button(); + PB_PokeDexBackground = new System.Windows.Forms.PictureBox(); + PB_PokeDexForeground = new System.Windows.Forms.PictureBox(); + B_PokeDexSkinSave = new System.Windows.Forms.Button(); + B_PokeDexForegroundLoad = new System.Windows.Forms.Button(); + B_PokeDexSkinLoad = new System.Windows.Forms.Button(); + B_PokeDexForegroundSave = new System.Windows.Forms.Button(); + Tab_Musical = new System.Windows.Forms.TabPage(); + B_MusicalExport = new System.Windows.Forms.Button(); + B_MusicalImport = new System.Windows.Forms.Button(); + Tab_BattleVideo = new System.Windows.Forms.TabPage(); + B_BattleVideoExportDecrypted = new System.Windows.Forms.Button(); + B_BattleVideoExport = new System.Windows.Forms.Button(); + B_BattleVideoImport = new System.Windows.Forms.Button(); + LB_BattleVideo = new System.Windows.Forms.ListBox(); + Tab_Pokestar = new System.Windows.Forms.TabPage(); + B_PokestarExport = new System.Windows.Forms.Button(); + B_PokestarImport = new System.Windows.Forms.Button(); + LB_Pokestar = new System.Windows.Forms.ListBox(); + Tab_PWT = new System.Windows.Forms.TabPage(); + B_PWTExport = new System.Windows.Forms.Button(); + B_PWTImport = new System.Windows.Forms.Button(); + LB_PWT = new System.Windows.Forms.ListBox(); + Tab_MemoryLink = new System.Windows.Forms.TabPage(); + B_Memory2Export = new System.Windows.Forms.Button(); + B_Memory2Import = new System.Windows.Forms.Button(); + B_Memory1Export = new System.Windows.Forms.Button(); + B_Memory1Import = new System.Windows.Forms.Button(); + Tab_BattleTest = new System.Windows.Forms.TabPage(); + B_BattleTestExport = new System.Windows.Forms.Button(); + B_BattleTestImport = new System.Windows.Forms.Button(); + ((System.ComponentModel.ISupportInitialize)PB_CGearBackground).BeginInit(); + TC_Tabs.SuspendLayout(); + Tab_CGear.SuspendLayout(); + Tab_PokeDex.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)PB_PokeDexBackground).BeginInit(); + ((System.ComponentModel.ISupportInitialize)PB_PokeDexForeground).BeginInit(); + Tab_Musical.SuspendLayout(); + Tab_BattleVideo.SuspendLayout(); + Tab_Pokestar.SuspendLayout(); + Tab_PWT.SuspendLayout(); + Tab_MemoryLink.SuspendLayout(); + Tab_BattleTest.SuspendLayout(); + SuspendLayout(); + // + // PB_CGearBackground + // + PB_CGearBackground.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + PB_CGearBackground.Location = new System.Drawing.Point(7, 6); + PB_CGearBackground.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + PB_CGearBackground.Name = "PB_CGearBackground"; + PB_CGearBackground.Size = new System.Drawing.Size(258, 194); + PB_CGearBackground.TabIndex = 0; + PB_CGearBackground.TabStop = false; + // + // B_Cancel + // + B_Cancel.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; + B_Cancel.Location = new System.Drawing.Point(407, 346); + B_Cancel.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + B_Cancel.Name = "B_Cancel"; + B_Cancel.Size = new System.Drawing.Size(120, 27); + B_Cancel.TabIndex = 1; + B_Cancel.Text = "Cancel"; + B_Cancel.UseVisualStyleBackColor = true; + B_Cancel.Click += B_Cancel_Click; + // + // B_Save + // + B_Save.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right; + B_Save.Location = new System.Drawing.Point(535, 346); + B_Save.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + B_Save.Name = "B_Save"; + B_Save.Size = new System.Drawing.Size(120, 27); + B_Save.TabIndex = 2; + B_Save.Text = "Save"; + B_Save.UseVisualStyleBackColor = true; + B_Save.Click += B_Save_Click; + // + // B_ImportPNG + // + B_ImportPNG.Location = new System.Drawing.Point(273, 6); + B_ImportPNG.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + B_ImportPNG.Name = "B_ImportPNG"; + B_ImportPNG.Size = new System.Drawing.Size(120, 32); + B_ImportPNG.TabIndex = 3; + B_ImportPNG.Text = "Load Image"; + B_ImportPNG.UseVisualStyleBackColor = true; + B_ImportPNG.Click += B_ImportPNGCGear_Click; + // + // B_ExportPNG + // + B_ExportPNG.Location = new System.Drawing.Point(273, 44); + B_ExportPNG.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + B_ExportPNG.Name = "B_ExportPNG"; + B_ExportPNG.Size = new System.Drawing.Size(120, 32); + B_ExportPNG.TabIndex = 4; + B_ExportPNG.Text = "Save Image"; + B_ExportPNG.UseVisualStyleBackColor = true; + B_ExportPNG.Click += B_ExportPNGCGear_Click; + // + // B_ExportCGB + // + B_ExportCGB.Location = new System.Drawing.Point(273, 168); + B_ExportCGB.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + B_ExportCGB.Name = "B_ExportCGB"; + B_ExportCGB.Size = new System.Drawing.Size(120, 32); + B_ExportCGB.TabIndex = 6; + B_ExportCGB.Text = "Export"; + B_ExportCGB.UseVisualStyleBackColor = true; + B_ExportCGB.Click += B_ExportCGB_Click; + // + // B_ImportCGB + // + B_ImportCGB.Location = new System.Drawing.Point(273, 130); + B_ImportCGB.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + B_ImportCGB.Name = "B_ImportCGB"; + B_ImportCGB.Size = new System.Drawing.Size(120, 32); + B_ImportCGB.TabIndex = 5; + B_ImportCGB.Text = "Import"; + B_ImportCGB.UseVisualStyleBackColor = true; + B_ImportCGB.Click += B_ImportCGB_Click; + // + // TC_Tabs + // + TC_Tabs.Alignment = System.Windows.Forms.TabAlignment.Left; + TC_Tabs.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + TC_Tabs.Controls.Add(Tab_CGear); + TC_Tabs.Controls.Add(Tab_PokeDex); + TC_Tabs.Controls.Add(Tab_BattleTest); + TC_Tabs.Controls.Add(Tab_Musical); + TC_Tabs.Controls.Add(Tab_BattleVideo); + TC_Tabs.Controls.Add(Tab_Pokestar); + TC_Tabs.Controls.Add(Tab_PWT); + TC_Tabs.Controls.Add(Tab_MemoryLink); + TC_Tabs.DrawMode = System.Windows.Forms.TabDrawMode.OwnerDrawFixed; + TC_Tabs.ItemSize = new System.Drawing.Size(40, 128); + TC_Tabs.Location = new System.Drawing.Point(0, 0); + TC_Tabs.Margin = new System.Windows.Forms.Padding(0); + TC_Tabs.Multiline = true; + TC_Tabs.Name = "TC_Tabs"; + TC_Tabs.Padding = new System.Drawing.Point(0, 0); + TC_Tabs.SelectedIndex = 0; + TC_Tabs.Size = new System.Drawing.Size(663, 339); + TC_Tabs.SizeMode = System.Windows.Forms.TabSizeMode.Fixed; + TC_Tabs.TabIndex = 7; + // + // Tab_CGear + // + Tab_CGear.Controls.Add(PB_CGearBackground); + Tab_CGear.Controls.Add(B_ExportCGB); + Tab_CGear.Controls.Add(B_ImportPNG); + Tab_CGear.Controls.Add(B_ImportCGB); + Tab_CGear.Controls.Add(B_ExportPNG); + Tab_CGear.Location = new System.Drawing.Point(132, 4); + Tab_CGear.Name = "Tab_CGear"; + Tab_CGear.Padding = new System.Windows.Forms.Padding(3); + Tab_CGear.Size = new System.Drawing.Size(527, 331); + Tab_CGear.TabIndex = 0; + Tab_CGear.Text = "C-Gear Skin"; + Tab_CGear.UseVisualStyleBackColor = true; + // + // Tab_PokeDex + // + Tab_PokeDex.Controls.Add(B_PokeDexBackgroundLoad); + Tab_PokeDex.Controls.Add(B_PokeDexBackgroundSave); + Tab_PokeDex.Controls.Add(PB_PokeDexBackground); + Tab_PokeDex.Controls.Add(PB_PokeDexForeground); + Tab_PokeDex.Controls.Add(B_PokeDexSkinSave); + Tab_PokeDex.Controls.Add(B_PokeDexForegroundLoad); + Tab_PokeDex.Controls.Add(B_PokeDexSkinLoad); + Tab_PokeDex.Controls.Add(B_PokeDexForegroundSave); + Tab_PokeDex.Location = new System.Drawing.Point(132, 4); + Tab_PokeDex.Name = "Tab_PokeDex"; + Tab_PokeDex.Padding = new System.Windows.Forms.Padding(3); + Tab_PokeDex.Size = new System.Drawing.Size(527, 331); + Tab_PokeDex.TabIndex = 1; + Tab_PokeDex.Text = "PokéDex Skin"; + Tab_PokeDex.UseVisualStyleBackColor = true; + // + // B_PokeDexBackgroundLoad + // + B_PokeDexBackgroundLoad.Location = new System.Drawing.Point(403, 200); + B_PokeDexBackgroundLoad.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + B_PokeDexBackgroundLoad.Name = "B_PokeDexBackgroundLoad"; + B_PokeDexBackgroundLoad.Size = new System.Drawing.Size(120, 32); + B_PokeDexBackgroundLoad.TabIndex = 13; + B_PokeDexBackgroundLoad.Text = "Load Image"; + B_PokeDexBackgroundLoad.UseVisualStyleBackColor = true; + // + // B_PokeDexBackgroundSave + // + B_PokeDexBackgroundSave.Location = new System.Drawing.Point(403, 238); + B_PokeDexBackgroundSave.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + B_PokeDexBackgroundSave.Name = "B_PokeDexBackgroundSave"; + B_PokeDexBackgroundSave.Size = new System.Drawing.Size(120, 32); + B_PokeDexBackgroundSave.TabIndex = 14; + B_PokeDexBackgroundSave.Text = "Save Image"; + B_PokeDexBackgroundSave.UseVisualStyleBackColor = true; + // + // PB_PokeDexBackground + // + PB_PokeDexBackground.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + PB_PokeDexBackground.Location = new System.Drawing.Point(265, 0); + PB_PokeDexBackground.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + PB_PokeDexBackground.Name = "PB_PokeDexBackground"; + PB_PokeDexBackground.Size = new System.Drawing.Size(258, 194); + PB_PokeDexBackground.TabIndex = 12; + PB_PokeDexBackground.TabStop = false; + // + // PB_PokeDexForeground + // + PB_PokeDexForeground.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + PB_PokeDexForeground.Location = new System.Drawing.Point(0, 0); + PB_PokeDexForeground.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + PB_PokeDexForeground.Name = "PB_PokeDexForeground"; + PB_PokeDexForeground.Size = new System.Drawing.Size(258, 194); + PB_PokeDexForeground.TabIndex = 7; + PB_PokeDexForeground.TabStop = false; + // + // B_PokeDexSkinSave + // + B_PokeDexSkinSave.Location = new System.Drawing.Point(265, 293); + B_PokeDexSkinSave.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + B_PokeDexSkinSave.Name = "B_PokeDexSkinSave"; + B_PokeDexSkinSave.Size = new System.Drawing.Size(120, 32); + B_PokeDexSkinSave.TabIndex = 11; + B_PokeDexSkinSave.Text = "Export"; + B_PokeDexSkinSave.UseVisualStyleBackColor = true; + B_PokeDexSkinSave.Click += B_PokeDexSkinSave_Click; + // + // B_PokeDexForegroundLoad + // + B_PokeDexForegroundLoad.Location = new System.Drawing.Point(7, 200); + B_PokeDexForegroundLoad.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + B_PokeDexForegroundLoad.Name = "B_PokeDexForegroundLoad"; + B_PokeDexForegroundLoad.Size = new System.Drawing.Size(120, 32); + B_PokeDexForegroundLoad.TabIndex = 8; + B_PokeDexForegroundLoad.Text = "Load Image"; + B_PokeDexForegroundLoad.UseVisualStyleBackColor = true; + // + // B_PokeDexSkinLoad + // + B_PokeDexSkinLoad.Location = new System.Drawing.Point(138, 293); + B_PokeDexSkinLoad.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + B_PokeDexSkinLoad.Name = "B_PokeDexSkinLoad"; + B_PokeDexSkinLoad.Size = new System.Drawing.Size(120, 32); + B_PokeDexSkinLoad.TabIndex = 10; + B_PokeDexSkinLoad.Text = "Import"; + B_PokeDexSkinLoad.UseVisualStyleBackColor = true; + B_PokeDexSkinLoad.Click += B_PokeDexSkinLoad_Click; + // + // B_PokeDexForegroundSave + // + B_PokeDexForegroundSave.Location = new System.Drawing.Point(7, 238); + B_PokeDexForegroundSave.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + B_PokeDexForegroundSave.Name = "B_PokeDexForegroundSave"; + B_PokeDexForegroundSave.Size = new System.Drawing.Size(120, 32); + B_PokeDexForegroundSave.TabIndex = 9; + B_PokeDexForegroundSave.Text = "Save Image"; + B_PokeDexForegroundSave.UseVisualStyleBackColor = true; + // + // Tab_Musical + // + Tab_Musical.Controls.Add(B_MusicalExport); + Tab_Musical.Controls.Add(B_MusicalImport); + Tab_Musical.Location = new System.Drawing.Point(132, 4); + Tab_Musical.Name = "Tab_Musical"; + Tab_Musical.Size = new System.Drawing.Size(527, 331); + Tab_Musical.TabIndex = 2; + Tab_Musical.Text = "Musical"; + Tab_Musical.UseVisualStyleBackColor = true; + // + // B_MusicalExport + // + B_MusicalExport.Location = new System.Drawing.Point(368, 3); + B_MusicalExport.Name = "B_MusicalExport"; + B_MusicalExport.Size = new System.Drawing.Size(120, 48); + B_MusicalExport.TabIndex = 11; + B_MusicalExport.Text = "Export"; + B_MusicalExport.UseVisualStyleBackColor = true; + B_MusicalExport.Click += B_MusicalExport_Click; + // + // B_MusicalImport + // + B_MusicalImport.Location = new System.Drawing.Point(243, 3); + B_MusicalImport.Name = "B_MusicalImport"; + B_MusicalImport.Size = new System.Drawing.Size(120, 48); + B_MusicalImport.TabIndex = 10; + B_MusicalImport.Text = "Import"; + B_MusicalImport.UseVisualStyleBackColor = true; + B_MusicalImport.Click += B_MusicalImport_Click; + // + // Tab_BattleVideo + // + Tab_BattleVideo.Controls.Add(B_BattleVideoExportDecrypted); + Tab_BattleVideo.Controls.Add(B_BattleVideoExport); + Tab_BattleVideo.Controls.Add(B_BattleVideoImport); + Tab_BattleVideo.Controls.Add(LB_BattleVideo); + Tab_BattleVideo.Location = new System.Drawing.Point(132, 4); + Tab_BattleVideo.Name = "Tab_BattleVideo"; + Tab_BattleVideo.Size = new System.Drawing.Size(527, 331); + Tab_BattleVideo.TabIndex = 3; + Tab_BattleVideo.Text = "Battle Videos"; + Tab_BattleVideo.UseVisualStyleBackColor = true; + // + // B_BattleVideoExportDecrypted + // + B_BattleVideoExportDecrypted.Location = new System.Drawing.Point(368, 111); + B_BattleVideoExportDecrypted.Name = "B_BattleVideoExportDecrypted"; + B_BattleVideoExportDecrypted.Size = new System.Drawing.Size(120, 48); + B_BattleVideoExportDecrypted.TabIndex = 10; + B_BattleVideoExportDecrypted.Text = "Export Decrypted"; + B_BattleVideoExportDecrypted.UseVisualStyleBackColor = true; + B_BattleVideoExportDecrypted.Click += B_BattleVideoExportDecrypted_Click; + // + // B_BattleVideoExport + // + B_BattleVideoExport.Location = new System.Drawing.Point(368, 3); + B_BattleVideoExport.Name = "B_BattleVideoExport"; + B_BattleVideoExport.Size = new System.Drawing.Size(120, 48); + B_BattleVideoExport.TabIndex = 8; + B_BattleVideoExport.Text = "Export"; + B_BattleVideoExport.UseVisualStyleBackColor = true; + B_BattleVideoExport.Click += B_BattleVideoExport_Click; + // + // B_BattleVideoImport + // + B_BattleVideoImport.Location = new System.Drawing.Point(243, 3); + B_BattleVideoImport.Name = "B_BattleVideoImport"; + B_BattleVideoImport.Size = new System.Drawing.Size(120, 48); + B_BattleVideoImport.TabIndex = 7; + B_BattleVideoImport.Text = "Import"; + B_BattleVideoImport.UseVisualStyleBackColor = true; + B_BattleVideoImport.Click += B_BattleVideoImport_Click; + // + // LB_BattleVideo + // + LB_BattleVideo.FormattingEnabled = true; + LB_BattleVideo.ItemHeight = 15; + LB_BattleVideo.Location = new System.Drawing.Point(3, 3); + LB_BattleVideo.Name = "LB_BattleVideo"; + LB_BattleVideo.Size = new System.Drawing.Size(234, 64); + LB_BattleVideo.TabIndex = 6; + LB_BattleVideo.SelectedIndexChanged += LB_BattleVideo_SelectedIndexChanged; + // + // Tab_Pokestar + // + Tab_Pokestar.Controls.Add(B_PokestarExport); + Tab_Pokestar.Controls.Add(B_PokestarImport); + Tab_Pokestar.Controls.Add(LB_Pokestar); + Tab_Pokestar.Location = new System.Drawing.Point(132, 4); + Tab_Pokestar.Name = "Tab_Pokestar"; + Tab_Pokestar.Size = new System.Drawing.Size(527, 331); + Tab_Pokestar.TabIndex = 5; + Tab_Pokestar.Text = "Pokéstar Studios"; + Tab_Pokestar.UseVisualStyleBackColor = true; + // + // B_PokestarExport + // + B_PokestarExport.Location = new System.Drawing.Point(368, 3); + B_PokestarExport.Name = "B_PokestarExport"; + B_PokestarExport.Size = new System.Drawing.Size(120, 48); + B_PokestarExport.TabIndex = 5; + B_PokestarExport.Text = "Export"; + B_PokestarExport.UseVisualStyleBackColor = true; + B_PokestarExport.Click += B_PokestarExport_Click; + // + // B_PokestarImport + // + B_PokestarImport.Location = new System.Drawing.Point(243, 3); + B_PokestarImport.Name = "B_PokestarImport"; + B_PokestarImport.Size = new System.Drawing.Size(120, 48); + B_PokestarImport.TabIndex = 4; + B_PokestarImport.Text = "Import"; + B_PokestarImport.UseVisualStyleBackColor = true; + B_PokestarImport.Click += B_PokestarImport_Click; + // + // LB_Pokestar + // + LB_Pokestar.FormattingEnabled = true; + LB_Pokestar.ItemHeight = 15; + LB_Pokestar.Location = new System.Drawing.Point(3, 3); + LB_Pokestar.Name = "LB_Pokestar"; + LB_Pokestar.Size = new System.Drawing.Size(234, 124); + LB_Pokestar.TabIndex = 3; + LB_Pokestar.SelectedIndexChanged += LB_Pokestar_SelectedIndexChanged; + // + // Tab_PWT + // + Tab_PWT.Controls.Add(B_PWTExport); + Tab_PWT.Controls.Add(B_PWTImport); + Tab_PWT.Controls.Add(LB_PWT); + Tab_PWT.Location = new System.Drawing.Point(132, 4); + Tab_PWT.Name = "Tab_PWT"; + Tab_PWT.Size = new System.Drawing.Size(527, 331); + Tab_PWT.TabIndex = 4; + Tab_PWT.Text = "PWT"; + Tab_PWT.UseVisualStyleBackColor = true; + // + // B_PWTExport + // + B_PWTExport.Location = new System.Drawing.Point(368, 3); + B_PWTExport.Name = "B_PWTExport"; + B_PWTExport.Size = new System.Drawing.Size(120, 48); + B_PWTExport.TabIndex = 2; + B_PWTExport.Text = "Export"; + B_PWTExport.UseVisualStyleBackColor = true; + B_PWTExport.Click += B_PWTExport_Click; + // + // B_PWTImport + // + B_PWTImport.Location = new System.Drawing.Point(243, 3); + B_PWTImport.Name = "B_PWTImport"; + B_PWTImport.Size = new System.Drawing.Size(120, 48); + B_PWTImport.TabIndex = 1; + B_PWTImport.Text = "Import"; + B_PWTImport.UseVisualStyleBackColor = true; + B_PWTImport.Click += B_PWTImport_Click; + // + // LB_PWT + // + LB_PWT.FormattingEnabled = true; + LB_PWT.ItemHeight = 15; + LB_PWT.Location = new System.Drawing.Point(3, 3); + LB_PWT.Name = "LB_PWT"; + LB_PWT.Size = new System.Drawing.Size(234, 49); + LB_PWT.TabIndex = 0; + LB_PWT.SelectedIndexChanged += LB_PWTIndex_SelectedIndexChanged; + // + // Tab_MemoryLink + // + Tab_MemoryLink.Controls.Add(B_Memory2Export); + Tab_MemoryLink.Controls.Add(B_Memory2Import); + Tab_MemoryLink.Controls.Add(B_Memory1Export); + Tab_MemoryLink.Controls.Add(B_Memory1Import); + Tab_MemoryLink.Location = new System.Drawing.Point(132, 4); + Tab_MemoryLink.Name = "Tab_MemoryLink"; + Tab_MemoryLink.Padding = new System.Windows.Forms.Padding(3); + Tab_MemoryLink.Size = new System.Drawing.Size(527, 331); + Tab_MemoryLink.TabIndex = 6; + Tab_MemoryLink.Text = "Memory Link"; + Tab_MemoryLink.UseVisualStyleBackColor = true; + // + // B_Memory2Export + // + B_Memory2Export.Location = new System.Drawing.Point(368, 57); + B_Memory2Export.Name = "B_Memory2Export"; + B_Memory2Export.Size = new System.Drawing.Size(120, 48); + B_Memory2Export.TabIndex = 6; + B_Memory2Export.Text = "Export Slot 2"; + B_Memory2Export.UseVisualStyleBackColor = true; + B_Memory2Export.Click += B_Memory2Export_Click; + // + // B_Memory2Import + // + B_Memory2Import.Location = new System.Drawing.Point(243, 57); + B_Memory2Import.Name = "B_Memory2Import"; + B_Memory2Import.Size = new System.Drawing.Size(120, 48); + B_Memory2Import.TabIndex = 5; + B_Memory2Import.Text = "Import Slot 2"; + B_Memory2Import.UseVisualStyleBackColor = true; + B_Memory2Import.Click += B_Memory2Import_Click; + // + // B_Memory1Export + // + B_Memory1Export.Location = new System.Drawing.Point(368, 3); + B_Memory1Export.Name = "B_Memory1Export"; + B_Memory1Export.Size = new System.Drawing.Size(120, 48); + B_Memory1Export.TabIndex = 4; + B_Memory1Export.Text = "Export Slot 1"; + B_Memory1Export.UseVisualStyleBackColor = true; + B_Memory1Export.Click += B_Memory1Export_Click; + // + // B_Memory1Import + // + B_Memory1Import.Location = new System.Drawing.Point(243, 3); + B_Memory1Import.Name = "B_Memory1Import"; + B_Memory1Import.Size = new System.Drawing.Size(120, 48); + B_Memory1Import.TabIndex = 3; + B_Memory1Import.Text = "Import Slot 1"; + B_Memory1Import.UseVisualStyleBackColor = true; + B_Memory1Import.Click += B_Memory1Import_Click; + // + // Tab_BattleTest + // + Tab_BattleTest.Controls.Add(B_BattleTestExport); + Tab_BattleTest.Controls.Add(B_BattleTestImport); + Tab_BattleTest.Location = new System.Drawing.Point(132, 4); + Tab_BattleTest.Name = "Tab_BattleTest"; + Tab_BattleTest.Padding = new System.Windows.Forms.Padding(3); + Tab_BattleTest.Size = new System.Drawing.Size(527, 331); + Tab_BattleTest.TabIndex = 7; + Tab_BattleTest.Text = "Battle Test"; + Tab_BattleTest.UseVisualStyleBackColor = true; + // + // B_BattleTestExport + // + B_BattleTestExport.Location = new System.Drawing.Point(368, 3); + B_BattleTestExport.Name = "B_BattleTestExport"; + B_BattleTestExport.Size = new System.Drawing.Size(120, 48); + B_BattleTestExport.TabIndex = 13; + B_BattleTestExport.Text = "Export"; + B_BattleTestExport.UseVisualStyleBackColor = true; + B_BattleTestExport.Click += B_BattleTestExport_Click; + // + // B_BattleTestImport + // + B_BattleTestImport.Location = new System.Drawing.Point(243, 3); + B_BattleTestImport.Name = "B_BattleTestImport"; + B_BattleTestImport.Size = new System.Drawing.Size(120, 48); + B_BattleTestImport.TabIndex = 12; + B_BattleTestImport.Text = "Import"; + B_BattleTestImport.UseVisualStyleBackColor = true; + B_BattleTestImport.Click += B_BattleTestImport_Click; + // + // SAV_DLC5 + // + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit; + ClientSize = new System.Drawing.Size(664, 381); + Controls.Add(TC_Tabs); + Controls.Add(B_Save); + Controls.Add(B_Cancel); + Icon = Properties.Resources.Icon; + Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + MaximizeBox = false; + MaximumSize = new System.Drawing.Size(1080, 720); + MinimumSize = new System.Drawing.Size(680, 420); + Name = "SAV_DLC5"; + StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + Text = "Generation 5 DLC I/O"; + ((System.ComponentModel.ISupportInitialize)PB_CGearBackground).EndInit(); + TC_Tabs.ResumeLayout(false); + Tab_CGear.ResumeLayout(false); + Tab_PokeDex.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)PB_PokeDexBackground).EndInit(); + ((System.ComponentModel.ISupportInitialize)PB_PokeDexForeground).EndInit(); + Tab_Musical.ResumeLayout(false); + Tab_BattleVideo.ResumeLayout(false); + Tab_Pokestar.ResumeLayout(false); + Tab_PWT.ResumeLayout(false); + Tab_MemoryLink.ResumeLayout(false); + Tab_BattleTest.ResumeLayout(false); + ResumeLayout(false); + } + + #endregion + + private System.Windows.Forms.PictureBox PB_CGearBackground; + private System.Windows.Forms.Button B_Cancel; + private System.Windows.Forms.Button B_Save; + private System.Windows.Forms.Button B_ImportPNG; + private System.Windows.Forms.Button B_ExportPNG; + private System.Windows.Forms.Button B_ExportCGB; + private System.Windows.Forms.Button B_ImportCGB; + private VerticalTabControl TC_Tabs; + private System.Windows.Forms.TabPage Tab_CGear; + private System.Windows.Forms.TabPage Tab_PokeDex; + private System.Windows.Forms.TabPage Tab_Musical; + private System.Windows.Forms.TabPage Tab_BattleVideo; + private System.Windows.Forms.TabPage Tab_PWT; + private System.Windows.Forms.TabPage Tab_Pokestar; + private System.Windows.Forms.TabPage Tab_MemoryLink; + private System.Windows.Forms.Button B_PWTExport; + private System.Windows.Forms.Button B_PWTImport; + private System.Windows.Forms.ListBox LB_PWT; + private System.Windows.Forms.Button B_PokestarExport; + private System.Windows.Forms.Button B_PokestarImport; + private System.Windows.Forms.ListBox LB_Pokestar; + private System.Windows.Forms.Button B_BattleVideoExport; + private System.Windows.Forms.Button B_BattleVideoImport; + private System.Windows.Forms.ListBox LB_BattleVideo; + private System.Windows.Forms.Button B_MusicalExport; + private System.Windows.Forms.Button B_MusicalImport; + private System.Windows.Forms.Button B_Memory2Export; + private System.Windows.Forms.Button B_Memory2Import; + private System.Windows.Forms.Button B_Memory1Export; + private System.Windows.Forms.Button B_Memory1Import; + private System.Windows.Forms.Button B_PokeDexBackgroundLoad; + private System.Windows.Forms.Button B_PokeDexBackgroundSave; + private System.Windows.Forms.PictureBox PB_PokeDexBackground; + private System.Windows.Forms.PictureBox PB_PokeDexForeground; + private System.Windows.Forms.Button B_PokeDexSkinSave; + private System.Windows.Forms.Button B_PokeDexForegroundLoad; + private System.Windows.Forms.Button B_PokeDexSkinLoad; + private System.Windows.Forms.Button B_PokeDexForegroundSave; + private System.Windows.Forms.Button B_BattleVideoExportDecrypted; + private System.Windows.Forms.TabPage Tab_BattleTest; + private System.Windows.Forms.Button B_BattleTestExport; + private System.Windows.Forms.Button B_BattleTestImport; + } +} diff --git a/PKHeX.WinForms/Subforms/Save Editors/Gen5/SAV_DLC5.cs b/PKHeX.WinForms/Subforms/Save Editors/Gen5/SAV_DLC5.cs new file mode 100644 index 000000000..46016a167 --- /dev/null +++ b/PKHeX.WinForms/Subforms/Save Editors/Gen5/SAV_DLC5.cs @@ -0,0 +1,459 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Windows.Forms; +using PKHeX.Core; + +namespace PKHeX.WinForms; + +public partial class SAV_DLC5 : Form +{ + private readonly SAV5 Origin; + private readonly SAV5 SAV; + + private readonly CGearBackground bg; + private readonly PokeDexSkin5 dex; + private const string CGearFilterBW = $"PokeStock C-Gear Skin Background|*.{CGearBackgroundBW.Extension}"; + private const string CGearFilterB2W2 = $"C-Gear Background|*.{CGearBackgroundB2W2.Extension}"; + + private const string PWTFileName = "PWT"; + private const string MusicalShowFileName = "Musical Show"; + private const string BattleVideoFileName = "Generation 5 Battle Video"; + private const string PokeStarMovieFileName = "Pokestar Studio Movie"; + private const string MemoryLinkFileName = "Memory Link"; + private const string PokeDexFileName = "Pokédex Skin"; + private const string BattleTestFileName = "Battle Test"; + + private const string MemoryLinkExtension = "ml5"; + + public SAV_DLC5(SAV5 sav) + { + InitializeComponent(); + WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage); + SAV = (SAV5)(Origin = sav).Clone(); + + var data = SAV.CGearSkinData; + bg = sav is SAV5BW ? new CGearBackgroundBW(data) : new CGearBackgroundB2W2(data); + dex = new PokeDexSkin5(sav.PokedexSkinData); + + LoadTabs(); + + Tab_BattleTest.Controls.Add(new Label + { + Text = "Not working. Needs research.", + ForeColor = Color.Red, Location = new(20,20), + AutoSize = true, + }); + } + + private void LoadTabs() + { + if (SAV is not SAV5B2W2 b2w2) + { + TC_Tabs.Controls.Remove(Tab_PWT); + TC_Tabs.Controls.Remove(Tab_Pokestar); + } + else + { + LoadPWT(b2w2); + LoadPokestar(b2w2); + } + + LoadCGear(SAV); + LoadPokedexSkin(SAV); + LoadBattleVideos(SAV); + LoadMusical(SAV); + LoadMemoryLink(SAV); + LoadBattleTest(SAV); + } + + private void LoadCGear(SAV5 sav) + { + if (bg.IsUninitialized) + { + B_ExportCGB.Enabled = B_ExportPNG.Enabled = false; + PB_CGearBackground.BackColor = Color.Black; + return; + } + PB_CGearBackground.Image = CGearImage.GetBitmap(bg); + } + + private void LoadPokedexSkin(SAV5 sav) + { + if (dex.IsUninitialized) + { + B_PokeDexSkinSave.Enabled = false; + PB_PokeDexBackground.BackColor = PB_PokeDexForeground.BackColor = Color.Black; + return; + } + LoadPokedexSkin(dex.Data); + } + + private void LoadPokedexSkin(ReadOnlySpan data) + { + // not implemented + } + + private void LoadBattleTest(SAV5 sav) + { + // not implemented + } + + private void LoadMusical(SAV5 sav) { } + private void LoadMemoryLink(SAV5 sav) { } + + private void LoadBattleVideos(SAV5 sav) + { + for (int i = 0; i < 4; i++) + { + var data = sav.GetBattleVideo(i); + var bvid = new BattleVideo5(data); + var name = bvid.IsUninitialized ? "Empty" : bvid.GetTrainerNames(); + LB_BattleVideo.Items.Add($"{i:00} - {name}"); + } + LB_BattleVideo.SelectedIndex = 0; + } + + private void LoadPokestar(SAV5B2W2 sav) + { + for (int i = 0; i < SAV5B2W2.PokestarCount; i++) + { + var data = sav.GetPokestarMovie(i); + var movie = new PokestarMovie5(data); + LB_Pokestar.Items.Add($"{i + 1:00} - {movie.Name}"); + } + LB_Pokestar.SelectedIndex = 0; + } + + private void LoadPWT(SAV5B2W2 sav) + { + for (int i = 0; i < SAV5B2W2.PWTCount; i++) + { + var data = sav.GetPWT(i); + var pwt = new WorldTournament5(data); + var name = pwt.Name; + if (string.IsNullOrWhiteSpace(name)) + name = "Empty"; + LB_PWT.Items.Add($"{i + 1:00} - {name}"); + } + LB_PWT.SelectedIndex = 0; + } + + private string? LastImportedFile = null; + + private bool ImportFile(string extension, string name, int expectSize, out byte[] data, string? initialName = null, int otherSize = -1) + { + data = []; + + using var ofd = new OpenFileDialog(); + ofd.Filter = $"{name}|*.{extension}"; + ofd.FileName = $"{initialName ?? name}.{extension}"; + if (ofd.ShowDialog() != DialogResult.OK) + return false; + var fi = new FileInfo(ofd.FileName); + if (fi.Length != expectSize && fi.Length != otherSize) + { + WinFormsUtil.Error($"Invalid file size. Expected {expectSize} bytes, got {fi.Length} bytes."); + return false; + } + + data = File.ReadAllBytes(ofd.FileName); + if (data.Length == otherSize) + Array.Resize(ref data, expectSize); + + System.Media.SystemSounds.Asterisk.Play(); + LastImportedFile = ofd.FileName; + return true; + } + + private static void ExportFile(string extension, string name, byte[] data, string? initialName = null) + { + using var sfd = new SaveFileDialog(); + sfd.Filter = $"{name}|*.{extension}"; + if (string.IsNullOrWhiteSpace(initialName)) + initialName = string.IsNullOrWhiteSpace(name) ? "Empty" : name; + sfd.FileName = $"{initialName}.{extension}"; + if (sfd.ShowDialog() != DialogResult.OK) + return; + File.WriteAllBytes(sfd.FileName, data); + } + + private void B_ImportPNGCGear_Click(object sender, EventArgs e) + { + using var ofd = new OpenFileDialog(); + ofd.Filter = "PNG File|*.png"; + ofd.FileName = "Background.png"; + if (ofd.ShowDialog() != DialogResult.OK) + return; + + try + { + using var img = (Bitmap)Image.FromFile(ofd.FileName); + if (!IsInputCorrect(img, out var msg)) + { + WinFormsUtil.Alert(msg); + return; + } + var result = CGearImage.GetCGearBackground(img, bg); + SAV.SetCGearSkin(bg.Data); + PB_CGearBackground.Image = CGearImage.GetBitmap(bg); // regenerate rather than reuse input + B_ExportCGB.Enabled = B_ExportPNG.Enabled = true; + if (CheckResult(result, out msg)) + System.Media.SystemSounds.Asterisk.Play(); + else + WinFormsUtil.Alert(msg); + } + catch (Exception ex) + { + WinFormsUtil.Error(ex.Message); + } + } + + private static bool IsInputCorrect(Bitmap img, [NotNullWhen(false)] out string? msg) where T : ITiledImage + { + if (img.Width != T.Width || img.Height != T.Height) + { + msg = $"Incorrect image dimensions. Expected {T.Width}x{T.Height}"; + return false; + } + if (img.PixelFormat != PixelFormat.Format32bppArgb) + { + msg = $"Incorrect image format. Expected {PixelFormat.Format32bppArgb}"; + return false; + } + msg = null; + return true; + } + + private static bool CheckResult(TiledImageStat result, [NotNullWhen(false)] out string? msg) + where T : ITiledImage + { + bool tooManyColors = result.ColorCount > T.ColorCount; + bool tooManyTiles = result.TileCount > T.TilePoolCount; + if (!tooManyColors && !tooManyTiles) + { + msg = null; + return true; // Success + } + + msg = ""; + if (tooManyColors) + msg += $"Too many colors. Expected: {T.ColorCount}, received {result.ColorCount}"; + if (tooManyTiles) + msg += (msg.Length != 0 ? Environment.NewLine : "") + $"Too many tiles. Expected {T.TilePoolCount}, received {result.TileCount}"; + return false; + } + + private void B_ExportPNGCGear_Click(object sender, EventArgs e) + { + using var sfd = new SaveFileDialog(); + sfd.Filter = "PNG File|*.png"; + sfd.FileName = "Background.png"; + if (sfd.ShowDialog() != DialogResult.OK) + return; + + PB_CGearBackground.Image.Save(sfd.FileName, ImageFormat.Png); + } + + private void B_ImportCGB_Click(object sender, EventArgs e) + { + using var ofd = new OpenFileDialog(); + bool isBW = SAV is SAV5BW; + ofd.Filter = isBW ? $"{CGearFilterBW}|{CGearFilterB2W2}" : $"{CGearFilterB2W2}|{CGearFilterBW}"; + + if (ofd.ShowDialog() != DialogResult.OK) + return; + + var path = ofd.FileName; + var len = new FileInfo(path).Length; + if (len != CGearBackground.SIZE) + { + WinFormsUtil.Error($"Incorrect size, got {len} bytes, expected {CGearBackground.SIZE} bytes."); + return; + } + + byte[] data = File.ReadAllBytes(path); + + // Load the data and adjust it to the correct game format if not matching. + CGearBackground temp = isBW ? new CGearBackgroundBW(data) : new CGearBackgroundB2W2(data); + bool isPSK = PaletteTileSelection.IsPaletteShiftFormat(temp.Arrange); + + try + { + if (isBW && !isPSK) + PaletteTileSelection.ConvertToShiftFormat(temp.Arrange); + else if (!isBW && isPSK) + PaletteTileSelection.ConvertFromShiftFormat(temp.Arrange); + } + catch (Exception ex) + { + WinFormsUtil.Error(ex.Message); + return; + } + + SAV.SetCGearSkin(temp.Data); + PB_CGearBackground.Image = CGearImage.GetBitmap(bg); + B_ExportCGB.Enabled = B_ExportPNG.Enabled = true; + } + + private void B_ExportCGB_Click(object sender, EventArgs e) + { + using var sfd = new SaveFileDialog(); + sfd.Filter = SAV is SAV5BW ? CGearFilterBW : CGearFilterB2W2; + + if (sfd.ShowDialog() != DialogResult.OK) + return; + File.WriteAllBytes(sfd.FileName, bg.Data.ToArray()); + } + + private void B_Save_Click(object sender, EventArgs e) + { + Origin.CopyChangesFrom(SAV); + Close(); + } + + private void B_Cancel_Click(object sender, EventArgs e) => Close(); + + private void B_PWTImport_Click(object sender, EventArgs e) + { + const int pporg = 0x1314; + if (!ImportFile(WorldTournament5.Extension, PWTFileName, WorldTournament5.SIZE, out var data, otherSize: pporg)) + return; + var index = LB_PWT.SelectedIndex; + var b2w2 = (SAV5B2W2)SAV; + b2w2.SetPWT(index, data); + var pwt = new WorldTournament5(data); + var name = pwt.Name; + if (string.IsNullOrWhiteSpace(name)) + name = "Empty"; + LB_PWT.Items[index] = $"{index + 1:00} - {name}"; + LB_PWTIndex_SelectedIndexChanged(sender, e); + } + + private void B_PWTExport_Click(object sender, EventArgs e) + { + var b2w2 = (SAV5B2W2)SAV; + var data = b2w2.GetPWT(LB_PWT.SelectedIndex); + var pwt = new WorldTournament5(data); + var name = pwt.Name; + if (string.IsNullOrWhiteSpace(name)) + name = "Empty"; + ExportFile(WorldTournament5.Extension, PWTFileName, data.ToArray(), name); + } + + private void B_MusicalImport_Click(object sender, EventArgs e) + { + var size = SAV is SAV5B2W2 ? MusicalShow5.SIZE_B2W2 : MusicalShow5.SIZE_BW; + const int pporg = 0x17D78; + if (!ImportFile(MusicalShow5.Extension, MusicalShowFileName, size, out var data, otherSize: pporg)) + return; + SAV.SetMusical(data); + if (LastImportedFile is { } name) + SAV.Musical.MusicalName = Path.GetFileNameWithoutExtension(name).Trim(); + } + + private void B_MusicalExport_Click(object sender, EventArgs e) + { + var data = SAV.MusicalDownloadData; + ExportFile(MusicalShow5.Extension, SAV.Musical.MusicalName, data.ToArray()); + } + + private void B_BattleVideoImport_Click(object sender, EventArgs e) + { + if (!ImportFile(BattleVideo5.Extension, BattleVideoFileName, BattleVideo5.SIZE, out var data)) + return; + bool decrypted = BattleVideo5.GetIsDecrypted(data); + var bvid = new BattleVideo5(data) { IsDecrypted = decrypted }; + bvid.Encrypt(); + bvid.RefreshChecksums(); + + var index = LB_BattleVideo.SelectedIndex; + SAV.SetBattleVideo(index, data); + var name = bvid.IsUninitialized ? "Empty" : bvid.GetTrainerNames(); + LB_BattleVideo.Items[index] = $"{index:00} - {name}"; + LB_BattleVideo_SelectedIndexChanged(sender, e); + } + + private void B_BattleVideoExport_Click(object sender, EventArgs e) + { + var index = LB_BattleVideo.SelectedIndex; + var data = SAV.GetBattleVideo(index); + ExportFile(BattleVideo5.Extension, BattleVideoFileName, data.ToArray()); + } + + private void B_BattleVideoExportDecrypted_Click(object sender, EventArgs e) + { + var index = LB_BattleVideo.SelectedIndex; + var data = SAV.GetBattleVideo(index); + var bvid = new BattleVideo5(data); + bool actual = !bvid.IsUninitialized; + if (actual) + bvid.Decrypt(); + ExportFile(BattleVideo5.Extension, BattleVideoFileName, data.ToArray()); + if (actual) + bvid.Encrypt(); + } + + private void B_PokestarImport_Click(object sender, EventArgs e) + { + if (!ImportFile(PokestarMovie5.Extension, PokeStarMovieFileName, PokestarMovie5.SIZE, out var data)) + return; + var index = LB_Pokestar.SelectedIndex; + var b2w2 = (SAV5B2W2)SAV; + b2w2.SetPokestarMovie(index, data); + var movie = new PokestarMovie5(data); + LB_Pokestar.Items[index] = $"{+1:00} - {movie.Name}"; + LB_Pokestar_SelectedIndexChanged(sender, e); + } + + private void B_PokestarExport_Click(object sender, EventArgs e) + { + var b2w2 = (SAV5B2W2)SAV; + var data = b2w2.GetPokestarMovie(LB_Pokestar.SelectedIndex); + ExportFile(PokestarMovie5.Extension, PokeStarMovieFileName, data.ToArray()); + } + + private void LB_Pokestar_SelectedIndexChanged(object sender, EventArgs e) { } + private void LB_PWTIndex_SelectedIndexChanged(object sender, EventArgs e) { } + private void LB_BattleVideo_SelectedIndexChanged(object sender, EventArgs e) { } + + private void B_Memory1Import_Click(object sender, EventArgs e) + { + if (!ImportFile(MemoryLinkExtension, MemoryLinkFileName, SAV.Link1Data.Length, out var data)) + return; + SAV.SetLink1Data(data); + } + + private void B_Memory2Import_Click(object sender, EventArgs e) + { + if (!ImportFile(MemoryLinkExtension, MemoryLinkFileName, SAV.Link2Data.Length, out var data)) + return; + SAV.SetLink2Data(data); + } + + private void B_Memory1Export_Click(object sender, EventArgs e) + => ExportFile(MemoryLinkExtension, MemoryLinkFileName, SAV.Link1Data.ToArray()); + private void B_Memory2Export_Click(object sender, EventArgs e) + => ExportFile(MemoryLinkExtension, MemoryLinkFileName, SAV.Link2Data.ToArray()); + private void B_PokeDexSkinSave_Click(object sender, EventArgs e) + => ExportFile(PokeDexSkin5.Extension, PokeDexFileName, SAV.PokedexSkinData.ToArray()); + private void B_BattleTestExport_Click(object sender, EventArgs e) + => ExportFile(BattleTest5.Extension, BattleTestFileName, SAV.BattleTest.ToArray()); + + private void B_PokeDexSkinLoad_Click(object sender, EventArgs e) + { + if (!ImportFile(PokeDexSkin5.Extension, PokeDexFileName, SAV.PokedexSkinData.Length, out var data)) + return; + SAV.SetPokeDexSkin(data); + LoadPokedexSkin(data); + } + + private void B_BattleTestImport_Click(object sender, EventArgs e) + { + if (!ImportFile(BattleTest5.Extension, BattleTestFileName, BattleTest5.SIZE, out var data)) + return; + SAV.SetBattleTest(data); + } +}