diff --git a/PKHeX/MainWindow/Main.cs b/PKHeX/MainWindow/Main.cs index 2412d2086..07cfad104 100644 --- a/PKHeX/MainWindow/Main.cs +++ b/PKHeX/MainWindow/Main.cs @@ -711,10 +711,30 @@ private void openFile(byte[] input, string path, string ext) #region PC/Box Data else if (BitConverter.ToUInt16(input, 4) == 0 && BitConverter.ToUInt32(input, 8) > 0 && PKX.getIsPKM(input.Length / SAV.BoxSlotCount / SAV.BoxCount) || PKX.getIsPKM(input.Length / SAV.BoxSlotCount)) { - if (SAV.setPCBin(input)) - Util.Alert("PC Binary loaded."); - else if (SAV.setBoxBin(input, CB_BoxSelect.SelectedIndex)) - Util.Alert("Box Binary loaded."); + if (SAV.getPCBin().Length == input.Length) + { + if (SAV.getBoxHasLockedSlot(0, SAV.BoxCount - 1)) + Util.Alert("Battle Box slots prevent loading of PC data."); + else if (SAV.setPCBin(input)) + Util.Alert("PC Binary loaded."); + else + { + Util.Alert("Binary is not compatible with save file.", "Current SAV Generation: " + SAV.Generation); + return; + } + } + else if (SAV.getBoxBin(CB_BoxSelect.SelectedIndex).Length == input.Length) + { + if (SAV.getBoxHasLockedSlot(CB_BoxSelect.SelectedIndex, CB_BoxSelect.SelectedIndex)) + Util.Alert("Battle Box slots in box prevent loading of box data."); + else if (SAV.setBoxBin(input, CB_BoxSelect.SelectedIndex)) + Util.Alert("Box Binary loaded."); + else + { + Util.Alert("Binary is not compatible with save file.", "Current SAV Generation: " + SAV.Generation); + return; + } + } else { Util.Alert("Binary is not compatible with save file.", "Current SAV Generation: " + SAV.Generation); @@ -737,8 +757,16 @@ private void openFile(byte[] input, string path, string ext) bool? noSetb = getPKMSetOverride(); PKM[] data = b.BattlePKMs; int offset = SAV.getBoxOffset(CB_BoxSelect.SelectedIndex); + int slotSkipped = 0; for (int i = 0; i < 24; i++) + { + if (SAV.getIsSlotLocked(CB_BoxSelect.SelectedIndex, i)) + { slotSkipped++; continue; } SAV.setStoredSlot(data[i], offset + i*SAV.SIZE_STORED, noSetb); + } + + if (slotSkipped > 0) + Util.Alert($"Skipped {slotSkipped} locked slot{(slotSkipped > 1 ? "s" : "")}."); setPKXBoxes(); updateBoxViewers(); @@ -3124,23 +3152,31 @@ private void clickBoxSort(object sender, EventArgs e) bool all = false; if (ModifierKeys == (Keys.Alt | Keys.Shift) && DialogResult.Yes == Util.Prompt(MessageBoxButtons.YesNo, "Clear ALL Boxes?!")) { + if (SAV.getBoxHasLockedSlot(0, SAV.BoxCount - 1)) + { Util.Alert("Battle Box slots prevent the clearing of all boxes."); return; } SAV.resetBoxes(); modified = "Boxes cleared!"; all = true; } else if (ModifierKeys == Keys.Alt && DialogResult.Yes == Util.Prompt(MessageBoxButtons.YesNo, "Clear Current Box?")) { + if (SAV.getBoxHasLockedSlot(CB_BoxSelect.SelectedIndex, CB_BoxSelect.SelectedIndex)) + { Util.Alert("Battle Box slots prevent the clearing of box."); return; } SAV.resetBoxes(CB_BoxSelect.SelectedIndex, CB_BoxSelect.SelectedIndex + 1); modified = "Current Box cleared!"; } else if (ModifierKeys == (Keys.Control | Keys.Shift) && DialogResult.Yes == Util.Prompt(MessageBoxButtons.YesNo, "Sort ALL Boxes?!")) { + if (SAV.getBoxHasLockedSlot(0, SAV.BoxCount - 1)) + { Util.Alert("Battle Box slots prevent the sorting of all boxes."); return; } SAV.sortBoxes(); modified = "Boxes sorted!"; all = true; } else if (ModifierKeys == Keys.Control && DialogResult.Yes == Util.Prompt(MessageBoxButtons.YesNo, "Sort Current Box?")) { + if (SAV.getBoxHasLockedSlot(CB_BoxSelect.SelectedIndex, CB_BoxSelect.SelectedIndex)) + { Util.Alert("Battle Box slots prevent the sorting of box."); return; } SAV.sortBoxes(CB_BoxSelect.SelectedIndex, CB_BoxSelect.SelectedIndex + 1); modified = "Current Box sorted!"; } @@ -3231,6 +3267,8 @@ private void clickSet(object sender, EventArgs e) int slot = getSlot(sender); if (slot == 30 && (CB_Species.SelectedIndex == 0 || CHK_IsEgg.Checked)) { Util.Alert("Can't have empty/egg first slot."); return; } + if (SAV.getIsSlotLocked(CB_BoxSelect.SelectedIndex, slot)) + { Util.Alert("Can't set to locked slot."); return; } int offset = getPKXOffset(slot); if (offset < 0) @@ -3279,7 +3317,10 @@ private void clickSet(object sender, EventArgs e) private void clickDelete(object sender, EventArgs e) { int slot = getSlot(sender); - if (slot == 30 && SAV.PartyCount == 1 && !HaX) { Util.Alert("Can't delete first slot."); return; } + if (slot == 30 && SAV.PartyCount == 1 && !HaX) + { Util.Alert("Can't delete first slot."); return; } + if (SAV.getIsSlotLocked(CB_BoxSelect.SelectedIndex, slot)) + { Util.Alert("Can't delete locked slot."); return; } int offset = getPKXOffset(slot); if (offset < 0) @@ -3382,11 +3423,18 @@ private void clickClone(object sender, EventArgs e) PKM pk = preparePKM(); + int slotSkipped = 0; for (int i = 0; i < 30; i++) // set to every slot in box { + if (SAV.getIsSlotLocked(CB_BoxSelect.SelectedIndex, i)) + { slotSkipped++; continue; } SAV.setStoredSlot(pk, getPKXOffset(i)); getQuickFiller(SlotPictureBoxes[i], pk); } + + if (slotSkipped > 0) + Util.Alert($"Skipped {slotSkipped} locked slot{(slotSkipped > 1 ? "s" : "")}."); + updateBoxViewers(); } private void clickLegality(object sender, EventArgs e) @@ -3709,7 +3757,13 @@ private void getQuickFiller(PictureBox pb, PKM pk = null) pk = pk ?? preparePKM(false); // don't perform control loss click if (pb == dragout) mnuLQR.Enabled = pk.Species != 0; // Species - pb.Image = pk.Species != 0 ? pk.Sprite : null; + + var sprite = pk.Species != 0 ? pk.Sprite : null; + int slot = getSlot(pb); + bool locked = slot < 30 && SAV.getIsSlotLocked(CB_BoxSelect.SelectedIndex, slot); + if (locked) + sprite = Util.LayerImage(sprite, Properties.Resources.locked, 5, 0, 1); + pb.Image = sprite; if (pb.BackColor == Color.Red) pb.BackColor = Color.Transparent; } @@ -3733,7 +3787,13 @@ private void getSlotFiller(int offset, PictureBox pb) return; } // Something stored in slot. Only display if species is valid. - pb.Image = p.Species == 0 ? null : p.Sprite; + + var sprite = p.Species != 0 ? p.Sprite : null; + int slot = getSlot(pb); + bool locked = slot < 30 && SAV.getIsSlotLocked(CB_BoxSelect.SelectedIndex, slot); + if (locked) + sprite = Util.LayerImage(sprite, Properties.Resources.locked, 5, 0, 1); + pb.Image = sprite; pb.BackColor = Color.Transparent; pb.Visible = true; } @@ -4089,17 +4149,21 @@ private void pbBoxSlot_MouseMove(object sender, MouseEventArgs e) if (pb.Image == null) return; + int slot = getSlot(pb); + int box = slot >= 30 ? -1 : CB_BoxSelect.SelectedIndex; + if (SAV.getIsSlotLocked(box, slot)) + return; + // Set flag to prevent re-entering. DragInfo.slotDragDropInProgress = true; DragInfo.slotSource = this; - DragInfo.slotSourceSlotNumber = getSlot(pb); - int offset = getPKXOffset(DragInfo.slotSourceSlotNumber); + DragInfo.slotSourceSlotNumber = slot; + DragInfo.slotSourceBoxNumber = box; + DragInfo.slotSourceOffset = getPKXOffset(DragInfo.slotSourceSlotNumber); // Prepare Data - DragInfo.slotPkmSource = SAV.getData(offset, SAV.SIZE_STORED); - DragInfo.slotSourceOffset = offset; - DragInfo.slotSourceBoxNumber = DragInfo.slotSourceSlotNumber >= 30 ? -1 : CB_BoxSelect.SelectedIndex; + DragInfo.slotPkmSource = SAV.getData(DragInfo.slotSourceOffset, SAV.SIZE_STORED); // Make a new file name based off the PID bool encrypt = ModifierKeys == Keys.Control; @@ -4161,6 +4225,12 @@ private void pbBoxSlot_DragDrop(object sender, DragEventArgs e) // Check for In-Dropped files (PKX,SAV,ETC) string[] files = (string[])e.Data.GetData(DataFormats.FileDrop); if (Directory.Exists(files[0])) { loadBoxesFromDB(files[0]); return; } + if (SAV.getIsSlotLocked(DragInfo.slotDestinationBoxNumber, DragInfo.slotDestinationSlotNumber)) + { + DragInfo.slotDestinationSlotNumber = -1; // Invalidate + Util.Alert("Unable to set to locked slot."); + return; + } if (DragInfo.slotSourceOffset < 0) // file { if (files.Length <= 0) diff --git a/PKHeX/PKM/PKM.cs b/PKHeX/PKM/PKM.cs index 541052444..b21da987c 100644 --- a/PKHeX/PKM/PKM.cs +++ b/PKHeX/PKM/PKM.cs @@ -258,6 +258,7 @@ public byte[] Write() // Derived public virtual int SpriteItem => HeldItem; public virtual bool IsShiny => TSV == PSV; + public virtual bool Locked { get { return false; } set { } } public int TrainerID7 => (int)((uint)(TID | (SID << 16)) % 1000000); public bool Gen7 => Version >= 30 && Version <= 31; public bool Gen6 => Version >= 24 && Version <= 29; diff --git a/PKHeX/Resources/img/misc/locked.png b/PKHeX/Resources/img/misc/locked.png index eca248197..763a05a29 100644 Binary files a/PKHeX/Resources/img/misc/locked.png and b/PKHeX/Resources/img/misc/locked.png differ diff --git a/PKHeX/Saves/SAV7.cs b/PKHeX/Saves/SAV7.cs index 354817b7a..be650750e 100644 --- a/PKHeX/Saves/SAV7.cs +++ b/PKHeX/Saves/SAV7.cs @@ -25,11 +25,19 @@ public SAV7(byte[] data = null) if (!Exportable) resetBoxes(); - var demo = new byte[0x4C4].SequenceEqual(Data.Skip(PCLayout).Take(0x4C4)); + var demo = new byte[0x4C4].SequenceEqual(Data.Skip(PCLayout).Take(0x4C4)); // up to Battle Box values if (demo) { PokeDex = -1; // Disabled } + int lockedCount = 0; + for (int i = 0; i < LockedSlots.Length; i++) + { + short val = BitConverter.ToInt16(Data, BattleBoxFlags + i * 2); + if (val >= 0) + LockedSlots[lockedCount++] = (val & 0xFF + BoxSlotCount*(val >> 8)) & 0xFFFF; + } + Array.Resize(ref LockedSlots, lockedCount); } // Configuration @@ -219,11 +227,13 @@ private void getSAVOffsets() PokeDexLanguageFlags = PokeDex + 0x550; WondercardData = WondercardFlags + 0x100; + BattleBoxFlags = PCLayout + 0x4C4; PCBackgrounds = PCLayout + 0x5C0; LastViewedBox = PCLayout + 0x5E3; PCFlags = PCLayout + 0x5E0; FashionLength = 0x1A08; + LockedSlots = new int[36]; } else // Empty input { @@ -245,6 +255,7 @@ private void getSAVOffsets() private int JoinFestaData { get; set; } = int.MinValue; private int PokeFinderSave { get; set; } = int.MinValue; private int BattleTree { get; set; } = int.MinValue; + private int BattleBoxFlags { get; set; } = int.MinValue; // Accessible as SAV7 public int TrainerCard { get; private set; } = 0x14000; @@ -863,6 +874,14 @@ public override int PartyCount protected set { Data[Party + 6 * SIZE_PARTY] = (byte)value; } } public override int BoxesUnlocked { get { return Data[PCFlags + 1]; } set { Data[PCFlags + 1] = (byte)value; } } + public override bool getIsSlotLocked(int box, int slot) + { + if (slot >= 30 || box > BoxCount) + return true; + + int slotIndex = slot + BoxSlotCount*box; + return LockedSlots.Any(s => s == slotIndex); + } public override int DaycareSeedSize => 32; // 128 bits public override int getDaycareSlotOffset(int loc, int slot) diff --git a/PKHeX/Saves/SaveFile.cs b/PKHeX/Saves/SaveFile.cs index 4291c7aca..c1c6a25a6 100644 --- a/PKHeX/Saves/SaveFile.cs +++ b/PKHeX/Saves/SaveFile.cs @@ -206,6 +206,7 @@ public PKM[] BattleBoxData for (int i = 0; i < data.Length; i++) { data[i] = getStoredSlot(BattleBox + SIZE_STORED * i); + data[i].Locked = BattleBoxLocked; if (data[i].Species == 0) return data.Take(i).ToArray(); } @@ -352,6 +353,7 @@ public virtual MysteryGiftAlbum GiftAlbum public virtual int BoxesUnlocked { get { return -1; } set { } } public virtual byte[] BoxFlags { get { return null; } set { } } public virtual int CurrentBox { get { return 0; } set { } } + protected int[] LockedSlots = new int[0]; protected virtual int getBoxWallpaperOffset(int box) { return -1; } public int getBoxWallpaper(int box) @@ -451,6 +453,11 @@ public void deletePartySlot(int slot) setStoredSlot(BlankPKM, getPartyOffset(5), false, false); PartyCount -= 1; } + public virtual bool getIsSlotLocked(int box, int slot) { return false; } + public bool getBoxHasLockedSlot(int BoxStart, int BoxEnd) + { + return LockedSlots.Any(slot => BoxStart*BoxSlotCount <= slot && slot < (BoxEnd + 1)*BoxSlotCount); + } public void sortBoxes(int BoxStart = 0, int BoxEnd = -1) { @@ -485,6 +492,8 @@ public void resetBoxes(int BoxStart = 0, int BoxEnd = -1) public byte[] getBoxBin(int box) { return BoxData.Skip(box*BoxSlotCount).Take(BoxSlotCount).SelectMany(pk => pk.EncryptedBoxData).ToArray(); } public bool setPCBin(byte[] data) { + if (LockedSlots.Any()) + return false; if (data.Length != getPCBin().Length) return false; @@ -506,6 +515,8 @@ public bool setPCBin(byte[] data) } public bool setBoxBin(byte[] data, int box) { + if (LockedSlots.Any(slot => box * BoxSlotCount <= slot && slot < (box + 1) * BoxSlotCount)) + return false; if (data.Length != getBoxBin(box).Length) return false; @@ -553,6 +564,6 @@ public void setData(byte[] input, int Offset) Edited = true; } - public virtual bool RequiresMemeCrypto { get { return false; } } + public virtual bool RequiresMemeCrypto => false; } } diff --git a/PKHeX/Subforms/Save Editors/SAV_BoxViewer.cs b/PKHeX/Subforms/Save Editors/SAV_BoxViewer.cs index 1ba0180d8..12ec9bf6c 100644 --- a/PKHeX/Subforms/Save Editors/SAV_BoxViewer.cs +++ b/PKHeX/Subforms/Save Editors/SAV_BoxViewer.cs @@ -112,12 +112,22 @@ private void getSlotFiller(int offset, PictureBox pb) return; } // Something stored in slot. Only display if species is valid. - pb.Image = p.Species == 0 ? null : p.Sprite; + var sprite = p.Species != 0 ? p.Sprite : null; + int slot = getSlot(pb); + bool locked = slot < 30 && SAV.getIsSlotLocked(CB_BoxSelect.SelectedIndex, slot); + if (locked) + sprite = Util.LayerImage(sprite, Properties.Resources.locked, 5, 0, 1); + pb.Image = sprite; pb.BackColor = Color.Transparent; } private void getQuickFiller(PictureBox pb, PKM pk) { - pb.Image = pk.Species == 0 ? null : pk.Sprite; + var sprite = pk.Species != 0 ? pk.Sprite : null; + int slot = getSlot(pb); + bool locked = slot < 30 && SAV.getIsSlotLocked(CB_BoxSelect.SelectedIndex, slot); + if (locked) + sprite = Util.LayerImage(sprite, Properties.Resources.locked, 5, 0, 1); + pb.Image = sprite; if (pb.BackColor == Color.Red) pb.BackColor = Color.Transparent; } @@ -157,18 +167,21 @@ private void pbBoxSlot_MouseMove(object sender, MouseEventArgs e) if (pb.Image == null) return; + int slot = getSlot(pb); + int box = slot >= 30 ? -1 : CB_BoxSelect.SelectedIndex; + if (SAV.getIsSlotLocked(box, slot)) + return; + // Set flag to prevent re-entering. DragInfo.slotDragDropInProgress = true; DragInfo.slotSource = this; - DragInfo.slotSourceSlotNumber = getSlot(pb); - int offset = getPKXOffset(DragInfo.slotSourceSlotNumber); + DragInfo.slotSourceSlotNumber = slot; + DragInfo.slotSourceBoxNumber = box; + DragInfo.slotSourceOffset = getPKXOffset(DragInfo.slotSourceSlotNumber); // Prepare Data - DragInfo.slotPkmSource = SAV.getData(offset, SAV.SIZE_STORED); - DragInfo.slotSourceOffset = offset; - DragInfo.slotSourceBoxNumber = CB_BoxSelect.SelectedIndex; - DragInfo.slotSource = this; + DragInfo.slotPkmSource = SAV.getData(DragInfo.slotSourceOffset, SAV.SIZE_STORED); // Make a new file name based off the PID byte[] dragdata = SAV.decryptPKM(DragInfo.slotPkmSource); @@ -225,6 +238,12 @@ private void pbBoxSlot_DragDrop(object sender, DragEventArgs e) // Check for In-Dropped files (PKX,SAV,ETC) string[] files = (string[])e.Data.GetData(DataFormats.FileDrop); if (Directory.Exists(files[0])) { return; } + if (SAV.getIsSlotLocked(DragInfo.slotDestinationBoxNumber, DragInfo.slotDestinationSlotNumber)) + { + DragInfo.slotDestinationSlotNumber = -1; // Invalidate + Util.Alert("Unable to set to locked slot."); + return; + } if (DragInfo.slotSourceOffset < 0) // file { if (files.Length <= 0) diff --git a/PKHeX/Util/ImageUtil.cs b/PKHeX/Util/ImageUtil.cs index 745c701c2..6bb9167ee 100644 --- a/PKHeX/Util/ImageUtil.cs +++ b/PKHeX/Util/ImageUtil.cs @@ -10,6 +10,8 @@ public static partial class Util // Image Layering/Blending Utility internal static Bitmap LayerImage(Image baseLayer, Image overLayer, int x, int y, double trans) { + if (baseLayer == null) + return overLayer as Bitmap; Bitmap img = new Bitmap(baseLayer.Width, baseLayer.Height); using (Graphics gr = Graphics.FromImage(img)) {