diff --git a/PKHeX.Core/Legality/Moves/Breeding/Chain/ChainBreedUtil.cs b/PKHeX.Core/Legality/Moves/Breeding/Chain/ChainBreedUtil.cs index 217436f25..49c0d6a5f 100644 --- a/PKHeX.Core/Legality/Moves/Breeding/Chain/ChainBreedUtil.cs +++ b/PKHeX.Core/Legality/Moves/Breeding/Chain/ChainBreedUtil.cs @@ -43,6 +43,7 @@ public static bool Summarize(GameStrings strings, ushort species, byte form, Gam return true; } + #pragma warning disable RCS1242 /// public static string Summarize(GameStrings strings, in ChainBreedTrace trace) { @@ -55,9 +56,10 @@ public static string Summarize(GameStrings strings, in ChainBreedTrace trace) public static void Summarize(GameStrings strings, in ChainBreedTrace trace, StringBuilder sb) { WriteHeader(trace, sb); - for (var i = trace.Steps.Length - 1; i >= 0; i--) + var steps = trace.Steps; + for (var i = steps.Length - 1; i >= 0; i--) { - var entry = trace.Steps[i]; + var entry = steps[i]; WriteEntry(entry, sb, strings, i); } } @@ -72,6 +74,7 @@ private static void WriteHeader(in ChainBreedTrace trace, StringBuilder sb) else sb.AppendLine($"{count} = {breed} 🥚 & {transfer} ♻️."); } + #pragma warning restore RCS1242 private static void WriteEntry(in ChainBreedStep entry, StringBuilder sb, GameStrings strings, int index) { diff --git a/PKHeX.Core/Legality/Settings/Specialized/GameSpecificSettings.cs b/PKHeX.Core/Legality/Settings/Specialized/GameSpecificSettings.cs index 9d4ae367f..556c16683 100644 --- a/PKHeX.Core/Legality/Settings/Specialized/GameSpecificSettings.cs +++ b/PKHeX.Core/Legality/Settings/Specialized/GameSpecificSettings.cs @@ -14,12 +14,12 @@ public sealed class GameSpecificSettings public sealed class GameSpecificSettings3 { /// - /// Pokemon Box: Ruby & Sapphire allows swapping of teams at a precise time, which can put Mythicals/Legends/Over-leveled into the team slots before the ribbon award sequence. + /// Pokemon Box: Ruby & Sapphire allows swapping of teams at a precise time, which can put Mythicals/Legends/Over-leveled into the team slots before the ribbon award sequence. /// /// /// https://projectpokemon.org/home/forums/topic/68163-how-to-give-ribbons-to-legendary-pok%C3%A9mon-using-pok%C3%A9mon-box-ruby-sapphire/ /// - [LocalizedDescription("Rule-tweak to allow the Pokemon Box: Ruby & Sapphire exploit to obtain Battle Tower ribbons illegitimately.")] + [LocalizedDescription("Rule-tweak to allow the Pokemon Box: Ruby && Sapphire exploit to obtain Battle Tower ribbons illegitimately.")] public bool AllowBattleTowerTeamSwap { get; set; } } diff --git a/PKHeX.Core/Saves/SAV2Stadium.cs b/PKHeX.Core/Saves/SAV2Stadium.cs index 68c9c35be..6cac0a25b 100644 --- a/PKHeX.Core/Saves/SAV2Stadium.cs +++ b/PKHeX.Core/Saves/SAV2Stadium.cs @@ -6,7 +6,7 @@ namespace PKHeX.Core; /// /// Pokémon Stadium 2 (Pokémon Stadium GS in Japan) /// -public sealed class SAV2Stadium : SAV_STADIUM, IBoxDetailName +public sealed class SAV2Stadium : SAV_STADIUM, IBoxDetailName, IStorageCleanup { public override int SaveRevision => Japanese ? 0 : 1; public override string SaveRevisionString => Japanese ? "-J" : "-U"; @@ -62,6 +62,7 @@ public sealed class SAV2Stadium : SAV_STADIUM, IBoxDetailName public SAV2Stadium(Memory data, bool japanese) : base(data, japanese, GetIsSwap(data.Span, japanese)) { Box = BoxStart; + ConditionBoxes(); } public SAV2Stadium(bool japanese = false) : base(japanese, SaveUtil.SIZE_G2STAD) @@ -85,6 +86,15 @@ protected override bool GetIsBoxChecksumValid(int box) return chk == actual; } + // Box Structure: + // 0x00: 1 byte: initialized (1) or not (0) + // 0x01: 1 byte: count of present slots + // 0x02: 2 bytes: reserved + // 0x04: 0x50 (String?) + // 0x10: 16 bytes: box name (0x50-terminated) + // 0x20: SK2[n] slots + // ....: 0x00, 0x00, u32 Magic, u16 Checksum + protected override void SetBoxMetadata(int box) { var bdata = GetBoxOffset(box); @@ -99,21 +109,23 @@ protected override void SetBoxMetadata(int box) } var boxOfs = bdata - ListHeaderSizeBox; - var slice = Data.Slice(boxOfs, ListHeaderSizeBox); - if (slice[0] == 0) + var header = Data.Slice(boxOfs, ListHeaderSizeBox); + if (header[0] == 0) { - slice[0] = 1; - slice[1] = (byte)count; - slice[4] = StringConverter2.TerminatorCode; + // Initialize with current count and a fake box name. This is only done for uninitialized boxes. + header[0] = 1; + header[4] = StringConverter2.TerminatorCode; + + // Write a fake box name for (int i = 0; i < 4; i++) - slice[0x10 + i] = (byte)(0xF6 + i); // 1234 - } - else - { - slice[1] = (byte)count; + header[0x10 + i] = (byte)(0xF6 + i); // 1234 + header[0x14] = StringConverter2.TerminatorCode; } + header[1] = (byte)count; } + private byte GetBoxSlotCount(int boxDataStart) => Data[boxDataStart - ListHeaderSizeBox + 1]; + protected override void SetBoxChecksum(int box) { var boxOfs = GetBoxOffset(box) - ListHeaderSizeBox; @@ -238,6 +250,63 @@ private static bool GetIsSwap(ReadOnlySpan data, bool japanese) return StadiumUtil.IsMagicPresentSwap(boxSpan, BoxSizeJ, MAGIC_FOOTER, 1); return StadiumUtil.IsMagicPresentSwap(boxSpan, BoxSizeU, MAGIC_FOOTER, 1); } + + private void ConditionBoxes() + { + var blank = BlankPKM; + for (int i = 0; i < BoxCount; i++) + { + // If the box is uninitialized, reset it to the right state. + var ofs = GetBoxOffset(i); + + // Wipe empty slots after the count; don't display ghost slots. + var count = GetBoxSlotCount(ofs); + if (count >= BoxSlotCount) + continue; // already full + + // Fill empty slots with blank PKM so that arbitrary reads are correct + // If you want to see the ghost slots, add your own code to `continue` instead of doing the loop. + for (int s = count; s < BoxSlotCount; s++) + { + var rel = ofs + (s * SIZE_STORED); + var slice = Data.Slice(rel, SIZE_STORED); + var species = slice[0]; + if (species == 0) // don't bother converting from internal->national + continue; // don't bother wiping already-empty slots. + WriteSlotBox(blank, slice); + } + } + } + + public bool FixStoragePreWrite() + { + // Compress the storage. + bool anyShifted = false; + // For each box, move present slots to the front. + for (int i = 0; i < BoxCount; i++) + { + int present = 0; + var ofs = GetBoxOffset(i); + for (int s = 0; s < BoxSlotCount; s++) + { + var rel = ofs + (s * SIZE_STORED); + var species = Data[rel]; + if (species == 0) + continue; + if (present != s) + { + anyShifted = true; + var upSlot = Data[(ofs + (present * SIZE_STORED))..]; + var src = Data.Slice(rel, SIZE_STORED); + src.CopyTo(upSlot); + // wipe the old slot + src.Clear(); + } + present++; + } + } + return anyShifted; + } } public enum Stadium2TeamType diff --git a/PKHeX.WinForms/Resources/text/lang_de.txt b/PKHeX.WinForms/Resources/text/lang_de.txt index 9c2d0e4b4..fff52f34b 100644 --- a/PKHeX.WinForms/Resources/text/lang_de.txt +++ b/PKHeX.WinForms/Resources/text/lang_de.txt @@ -377,7 +377,7 @@ KChart.DGV_SpecName=Name KChart.DGV_Sprite=Sprite KChart.DGV_Type1=Typ1 KChart.DGV_Type2=Typ2 -LocalizedDescription.AllowBattleTowerTeamSwap=Regeländerung, um den Pokémon Box: Rubin & Saphir-Exploit zu erlauben, mit dem Battle Tower-Bänder unrechtmäßig erhalten werden können. +LocalizedDescription.AllowBattleTowerTeamSwap=Regeländerung, um den Pokémon Box: Rubin && Saphir-Exploit zu erlauben, mit dem Battle Tower-Bänder unrechtmäßig erhalten werden können. LocalizedDescription.AllowBoxDataDrop=Ermöglicht das Drag-and-Drop von Box-Binärdateien aus der Benutzeroberfläche über den Box-Tab. LocalizedDescription.AllowGen1Tradeback=Erlaube Gen 1 Rücktausch LocalizedDescription.AllowGuessRejuvenateHOME=Erlaube der PKM Konvertierung, legale Begegnungs Daten, welche nicht im vorherigen Format gespeichert sind, zu erraten. diff --git a/PKHeX.WinForms/Resources/text/lang_en.txt b/PKHeX.WinForms/Resources/text/lang_en.txt index 5f6451fd7..2f64fd9a1 100644 --- a/PKHeX.WinForms/Resources/text/lang_en.txt +++ b/PKHeX.WinForms/Resources/text/lang_en.txt @@ -377,7 +377,7 @@ KChart.DGV_SpecName=Name KChart.DGV_Sprite=Sprite KChart.DGV_Type1=Type1 KChart.DGV_Type2=Type2 -LocalizedDescription.AllowBattleTowerTeamSwap=Allow the Pokemon Box: Ruby & Sapphire exploit to obtain Battle Tower ribbons illegitimately. +LocalizedDescription.AllowBattleTowerTeamSwap=Allow the Pokemon Box: Ruby && Sapphire exploit to obtain Battle Tower ribbons illegitimately. LocalizedDescription.AllowBoxDataDrop=Allow drag and drop of boxdata binary files from the GUI via the Box tab. LocalizedDescription.AllowGen1Tradeback=GB: Allow Generation 2 tradeback learnsets LocalizedDescription.AllowGuessRejuvenateHOME=Allow PKM file conversion paths to guess the legal original encounter data that is not stored in the format that it was converted from.