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.