using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; using System.Media; using System.Windows.Forms; using PKHeX.Core; using PKHeX.Drawing; using static PKHeX.Core.MessageStrings; namespace PKHeX.WinForms.Controls; public partial class SAVEditor : UserControl, ISlotViewer, ISaveFileProvider { public SaveDataEditor EditEnv = null!; public void SetEditEnvironment(SaveDataEditor value) { EditEnv = value; M.Env = value; menu.Editor = value; SAV = value.SAV; value.Slots.Publisher.Subscribers.Add(this); value.Slots.Publisher.Subscribers.Add(SL_Party); value.Slots.Publisher.Subscribers.Add(Box); value.Slots.Publisher.Subscribers.Add(SL_Extra); } public SaveFile SAV { get; private set; } = new FakeSaveFile(); public int CurrentBox => Box.CurrentBox; public SlotChangeManager M { get; } public readonly ContextMenuSAV menu; public readonly BoxMenuStrip SortMenu; public bool HaX; public bool ModifyPKM { private get; set; } private bool _hideSecret; public bool HideSecretDetails { private get => _hideSecret; set => ToggleSecrets(SAV, _hideSecret = value); } public ToolStripMenuItem Menu_Redo { get; set; } = null!; public ToolStripMenuItem Menu_Undo { get; set; } = null!; private bool FieldsLoaded; public IList SlotPictureBoxes { get; } public int ViewIndex { get; set; } = -1; public bool FlagIllegal { get => Box.FlagIllegal; set { SL_Extra.FlagIllegal = SL_Party.FlagIllegal = Box.FlagIllegal = value && !HaX; if (SAV is FakeSaveFile) return; ReloadSlots(); } } public void ReloadSlots() { UpdateBoxViewers(all: true); ResetNonBoxSlots(); } public SAVEditor() { InitializeComponent(); L_SlotOccupied = new[] { L_DC1, L_DC2 }; TB_SlotEXP = new[] { TB_Daycare1XP, TB_Daycare2XP }; L_SlotEXP = new[] { L_XP1, L_XP2 }; SlotPictureBoxes = new[] { dcpkx1, dcpkx2 }; Tab_Box.ContextMenuStrip = SortMenu = new BoxMenuStrip(this); M = new SlotChangeManager(this) {Env = EditEnv}; Box.Setup(M); SL_Party.Setup(M); SL_Extra.ViewIndex = -2; menu = new ContextMenuSAV { Manager = M }; InitializeEvents(); } private void InitializeEvents() { foreach (PictureBox pb in SlotPictureBoxes) { InitializeDragDrop(pb); pb.ContextMenuStrip = menu.mnuVSD; } GiveFeedback += (sender, e) => e.UseDefaultCursors = false; Tab_Box.MouseWheel += (s, e) => { if (menu.mnuVSD.Visible) return; Box.CurrentBox = e.Delta > 1 ? Box.Editor.MoveLeft() : Box.Editor.MoveRight(); }; GB_Daycare.Click += (o, args) => SwitchDaycare(GB_Daycare, args); FLP_SAVtools.Scroll += WinFormsUtil.PanelScroll; SortMenu.Opening += (s, x) => x.Cancel = !tabBoxMulti.GetTabRect(tabBoxMulti.SelectedIndex).Contains(PointToClient(MousePosition)); } private void InitializeDragDrop(Control pb) { pb.MouseEnter += M.MouseEnter; pb.MouseLeave += M.MouseLeave; pb.MouseClick += M.MouseClick; pb.MouseMove += M.MouseMove; pb.MouseDown += M.MouseDown; pb.MouseUp += M.MouseUp; pb.DragEnter += M.DragEnter; pb.DragDrop += M.DragDrop; pb.QueryContinueDrag += M.QueryContinueDrag; pb.GiveFeedback += (sender, e) => e.UseDefaultCursors = false; pb.AllowDrop = true; pb.ContextMenuStrip = menu.mnuVSD; } /// Occurs when the Control Collection requests a cloning operation to the current box. public event EventHandler? RequestCloneData; /// Occurs when the Control Collection requests a save to be reloaded. public event EventHandler? RequestReloadSave; public void EnableDragDrop(DragEventHandler enter, DragEventHandler drop) { AllowDrop = true; DragDrop += drop; foreach (var tab in tabBoxMulti.TabPages.OfType()) { tab.AllowDrop = true; tab.DragEnter += enter; tab.DragDrop += drop; } M.Drag.RequestExternalDragDrop += drop; } // Generic Subfunctions // public int GetSlotOffset(int slot) { return SAV.GetDaycareSlotOffset(SAV.DaycareIndex, slot); } public int SwapBoxesViewer(int viewBox) { int mainBox = Box.CurrentBox; Box.CurrentBox = viewBox; return mainBox; } public void UpdateBoxViewers(bool all = false) { foreach (var v in M.Boxes.Where(v => v.CurrentBox == Box.CurrentBox || all)) { v.FlagIllegal = Box.FlagIllegal; v.ResetSlots(); } } public void NotifySlotOld(ISlotInfo previous) { var index = GetViewIndex(previous); if (index < 0) return; var pb = SlotPictureBoxes[index]; pb.BackgroundImage = null; } public int GetViewIndex(ISlotInfo slot) { for (int i = 0; i < SlotPictureBoxes.Count; i++) { var data = GetSlotData(i); if (data.Equals(slot)) return i; } return -1; } public void NotifySlotChanged(ISlotInfo slot, SlotTouchType type, PKM pk) { var index = GetViewIndex(slot); if (index < 0) return; if (type.IsContentChange() && slot is SlotInfoParty) ResetParty(); // lots of slots change, just update var pb = SlotPictureBoxes[index]; SlotUtil.UpdateSlot(pb, slot, pk, SAV, Box.FlagIllegal, type); } public ISlotInfo GetSlotData(PictureBox view) { var index = SlotPictureBoxes.IndexOf(view); return GetSlotData(index); } public ISlotInfo GetSlotData(int index) { var ofs = GetSlotOffset(index); return new SlotInfoMisc(SAV, index, ofs); } public void SetPKMBoxes() { if (SAV.HasBox) Box.ResetSlots(); ResetNonBoxSlots(); } private void ResetNonBoxSlots() { ResetParty(); ResetDaycare(); ResetMiscSlots(); } private void ResetMiscSlots() { var slots = SL_Extra.SlotPictureBoxes; for (int i = 0; i < SL_Extra.SlotCount; i++) { var info = SL_Extra.GetSlotData(i); var pb = slots[i]; SlotUtil.UpdateSlot(pb, info, info.Read(SAV), SAV, Box.FlagIllegal); } } private void ResetParty() { if (SAV.HasParty) SL_Party.ResetSlots(); } private readonly Label[] L_SlotOccupied; private readonly TextBox[] TB_SlotEXP; private readonly Label[] L_SlotEXP; private void ResetDaycare() { if (!SAV.HasDaycare) return; for (int i = 0; i < 2; i++) { var relIndex = i; var pb = UpdateSlot(relIndex); uint? exp = SAV.GetDaycareEXP(SAV.DaycareIndex, i); TB_SlotEXP[i].Visible = L_SlotEXP[i].Visible = exp != null; TB_SlotEXP[i].Text = exp.ToString(); bool? occ = SAV.IsDaycareOccupied(SAV.DaycareIndex, i); L_SlotOccupied[i].Visible = occ != null; if (occ == true) // If Occupied { L_SlotOccupied[i].Text = $"{i + 1}: ✓"; } else { L_SlotOccupied[i].Text = $"{i + 1}: ✘"; var current = pb.Image; if (current != null) pb.Image = ImageUtil.ChangeOpacity(current, 0.6); } } bool? egg = SAV.IsDaycareHasEgg(SAV.DaycareIndex); DayCare_HasEgg.Visible = egg != null; DayCare_HasEgg.Checked = egg == true; var seed = SAV.GetDaycareRNGSeed(SAV.DaycareIndex); bool hasSeed = !string.IsNullOrEmpty(seed); if (hasSeed) { TB_RNGSeed.MaxLength = SAV.DaycareSeedSize; TB_RNGSeed.Text = seed; } L_DaycareSeed.Visible = TB_RNGSeed.Visible = hasSeed; } private PictureBox UpdateSlot(int relIndex) { var info = GetSlotData(relIndex); var pb = SlotPictureBoxes[relIndex]; SlotUtil.UpdateSlot(pb, info, info.Read(SAV), SAV, Box.FlagIllegal); return pb; } public void SetParty() => ResetParty(); public void ClickUndo() { EditEnv.Slots.Undo(); UpdateUndoRedo(); } public void ClickRedo() { EditEnv.Slots.Redo(); UpdateUndoRedo(); } public void UpdateUndoRedo() { Menu_Undo.Enabled = EditEnv.Slots.Changelog.CanUndo; Menu_Redo.Enabled = EditEnv.Slots.Changelog.CanRedo; } public void SetClonesToBox(PKM pk) { if (WinFormsUtil.Prompt(MessageBoxButtons.YesNo, string.Format(MsgSaveBoxCloneFromTabs, Box.CurrentBoxName)) != DialogResult.Yes) return; int slotSkipped = SetClonesToCurrentBox(pk, Box.CurrentBox); if (slotSkipped > 0) WinFormsUtil.Alert(string.Format(MsgSaveBoxImportSkippedLocked, slotSkipped)); UpdateBoxViewers(); } private int SetClonesToCurrentBox(PKM pk, int box) { var arr = new PKM[SAV.BoxSlotCount]; for (int i = 0; i < SAV.BoxSlotCount; i++) // set to every slot in box arr[i] = pk; int slotSkipped = SAV.SetBoxData(arr, box); Box.ResetSlots(); return slotSkipped; } public void ClickSlot(object sender, EventArgs e) { switch (ModifierKeys) { case Keys.Control | Keys.Alt: ClickClone(sender, e); break; default: // forward to contextmenu for default behavior menu.OmniClick(sender, e, ModifierKeys); break; } } private void ClickBoxSort(object sender, MouseEventArgs e) { if (tabBoxMulti.SelectedTab != Tab_Box) return; if (!tabBoxMulti.GetTabRect(tabBoxMulti.SelectedIndex).Contains(PointToClient(MousePosition))) return; if ((e.Button & MouseButtons.Right) == 0) { if ((ModifierKeys & Keys.Alt) != 0) SortMenu.Clear(); else if ((ModifierKeys & Keys.Control) != 0) SortMenu.Sort(); return; } var pt = Tab_Box.PointToScreen(new Point(0, 0)); SortMenu.Show(pt); } public void FinishBoxManipulation(string message, bool all, int count) { SetPKMBoxes(); UpdateBoxViewers(all); if (!string.IsNullOrWhiteSpace(message)) WinFormsUtil.Alert(message + $" ({count})"); else SystemSounds.Asterisk.Play(); } private void ClickBoxDouble(object sender, MouseEventArgs e) { if (tabBoxMulti.SelectedTab == Tab_SAV) { RequestReloadSave?.Invoke(sender, e); return; } if (tabBoxMulti.SelectedTab != Tab_Box) return; if (!SAV.HasBox) return; if (ModifierKeys == Keys.Shift) { if (M.Boxes.Count > 1) // subview open { // close all subviews for (int i = 1; i < M.Boxes.Count; i++) M.Boxes[i].ParentForm?.Close(); } new SAV_BoxList(this, M).Show(); return; } if (M.Boxes.Count > 1) // subview open { var z = M.Boxes[1].ParentForm; if (z == null) return; z.CenterToForm(ParentForm); z.BringToFront(); return; } new SAV_BoxViewer(this, M).Show(); } private void ClickClone(object sender, EventArgs e) { var detail = Box.GetSlotData((PictureBox) sender); if (detail is SlotInfoBox) RequestCloneData?.Invoke(sender, e); } private void UpdateSaveSlot(object sender, EventArgs e) { if (SAV is not SAV4BR br) return; br.CurrentSlot = WinFormsUtil.GetIndex(CB_SaveSlot); Box.ResetBoxNames(); // fix box names SetPKMBoxes(); UpdateBoxViewers(true); } private void UpdateStringSeed(object sender, EventArgs e) { if (!FieldsLoaded) return; if (sender is not TextBox tb) return; if (string.IsNullOrWhiteSpace(tb.Text)) { tb.Undo(); return; } string filterText = Util.GetOnlyHex(tb.Text); if (string.IsNullOrWhiteSpace(filterText) || filterText.Length != tb.Text.Length) { WinFormsUtil.Alert(MsgProgramErrorExpectedHex, tb.Text); tb.Undo(); return; } // Write final value back to the save if (tb == TB_RNGSeed) { var value = filterText.PadLeft(SAV.DaycareSeedSize, '0'); SAV.SetDaycareRNGSeed(SAV.DaycareIndex, value); SAV.State.Edited = true; } else if (tb == TB_GameSync && SAV is IGameSync sync) { var value = filterText.PadLeft(sync.GameSyncIDSize, '0'); sync.GameSyncID = value; SAV.State.Edited = true; } else if (SAV is ISecureValueStorage s) { var value = Convert.ToUInt64(filterText, 16); if (tb == TB_Secure1) s.TimeStampCurrent = value; else if (tb == TB_Secure2) s.TimeStampPrevious = value; SAV.State.Edited = true; } } private void SwitchDaycare(object sender, EventArgs e) { if (!SAV.HasTwoDaycares) return; var current = string.Format(MsgSaveSwitchDaycareCurrent, SAV.DaycareIndex + 1); if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgSaveSwitchDaycareView, current)) return; SAV.DaycareIndex ^= 1; ResetDaycare(); } private void B_SaveBoxBin_Click(object sender, EventArgs e) { if (!SAV.HasBox) { WinFormsUtil.Alert(MsgSaveBoxFailNone); return; } Box.SaveBoxBinary(); } // Subfunction Save Buttons // private static void OpenDialog(Form f) { f.ShowDialog(); f.Dispose(); } private void B_OpenWondercards_Click(object sender, EventArgs e) => OpenDialog(new SAV_Wondercard(SAV, sender as DataMysteryGift)); private void B_OpenPokepuffs_Click(object sender, EventArgs e) => OpenDialog(new SAV_Pokepuff(SAV)); private void B_OpenPokeBeans_Click(object sender, EventArgs e) => OpenDialog(new SAV_Pokebean(SAV)); private void B_OpenItemPouch_Click(object sender, EventArgs e) => OpenDialog(new SAV_Inventory(SAV)); private void B_OpenBerryField_Click(object sender, EventArgs e) => OpenDialog(new SAV_BerryFieldXY((SAV6XY)SAV)); private void B_OpenPokeblocks_Click(object sender, EventArgs e) => OpenDialog(new SAV_PokeBlockORAS(SAV)); private void B_OpenSuperTraining_Click(object sender, EventArgs e) => OpenDialog(new SAV_SuperTrain(SAV)); private void B_OpenSecretBase_Click(object sender, EventArgs e) => OpenDialog(new SAV_SecretBase(SAV)); private void B_CellsStickers_Click(object sender, EventArgs e) => OpenDialog(new SAV_ZygardeCell(SAV)); private void B_LinkInfo_Click(object sender, EventArgs e) => OpenDialog(new SAV_Link6(SAV)); private void B_Roamer_Click(object sender, EventArgs e) => OpenDialog(new SAV_Roamer3(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(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)); private void B_OpenEventFlags_Click(object sender, EventArgs e) { using var form = SAV switch { SAV1 s => (Form) new SAV_EventReset1(s), SAV7b s => new SAV_EventWork(s), SAV8BS s => new SAV_FlagWork8b(s), IEventFlag37 g37 => new SAV_EventFlags(g37), SAV2 s => new SAV_EventFlags2(s), _ => throw new Exception(), }; form.ShowDialog(); } private void B_OpenBoxLayout_Click(object sender, EventArgs e) { OpenDialog(new SAV_BoxLayout(SAV, Box.CurrentBox)); Box.ResetBoxNames(); // fix box names Box.ResetSlots(); // refresh box background UpdateBoxViewers(all: true); // update subviewers } private static Form GetTrainerEditor(SaveFile sav) => sav switch { SAV6 s6 => new SAV_Trainer(s6), SAV7 s7 => new SAV_Trainer7(s7), SAV7b b7 => new SAV_Trainer7GG(b7), SAV8SWSH swsh => new SAV_Trainer8(swsh), SAV8BS bs => new SAV_Trainer8b(bs), SAV8LA la => new SAV_Trainer8a(la), _ => new SAV_SimpleTrainer(sav), }; private void B_OpenRaids_Click(object sender, EventArgs e) { if (SAV is not SAV8SWSH swsh) return; if (sender == B_Raids) OpenDialog(new SAV_Raid8(swsh, swsh.Raid)); else if (sender == B_RaidArmor) OpenDialog(new SAV_Raid8(swsh, swsh.RaidArmor)); else OpenDialog(new SAV_Raid8(swsh, swsh.RaidCrown)); } private void B_OtherSlots_Click(object sender, EventArgs e) { void TryOpen(SaveFile sav, IReadOnlyList g) { var form = WinFormsUtil.FirstFormOfType(); if (form != null) form.CenterToForm(ParentForm); else form = new SAV_GroupViewer(sav, M.Env.PKMEditor, g) { TopMost = true }; form.BringToFront(); form.Show(); } if (SAV is SAV_STADIUM s0) TryOpen(s0, s0.GetRegisteredTeams()); } private void B_Blocks_Click(object sender, EventArgs e) { var form = GetAccessorForm(SAV); form.ShowDialog(); form.Dispose(); } private static Form GetAccessorForm(SaveFile sav) => sav switch { SAV5BW s => new SAV_Accessor(s, s.Blocks), SAV5B2W2 s => new SAV_Accessor(s, s.Blocks), SAV6XY s => new SAV_Accessor(s, s.Blocks), SAV6AO s => new SAV_Accessor(s, s.Blocks), SAV6AODemo s => new SAV_Accessor(s, s.Blocks), SAV7SM s => new SAV_Accessor(s, s.Blocks), SAV7USUM s => new SAV_Accessor(s, s.Blocks), SAV7b s => new SAV_Accessor(s, s.Blocks), ISCBlockArray s => new SAV_BlockDump8(s), _ => GetPropertyForm(sav), }; private static Form GetPropertyForm(object sav) { var form = new Form { Text = "Simple Editor", StartPosition = FormStartPosition.CenterParent, MinimumSize = new Size(350, 380), MinimizeBox = false, MaximizeBox = false, Icon = Properties.Resources.Icon, }; var pg = new PropertyGrid {SelectedObject = sav, Dock = DockStyle.Fill}; form.Controls.Add(pg); return form; } private void B_OpenFriendSafari_Click(object sender, EventArgs e) { if (SAV is not SAV6XY xy) return; var dr = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgSaveGen6FriendSafari, MsgSaveGen6FriendSafariCheatDesc); if (dr == DialogResult.Yes) xy.UnlockAllFriendSafariSlots(); } private void B_OpenPokedex_Click(object sender, EventArgs e) { using var form = SAV switch { SAV1 s1 => new SAV_SimplePokedex(s1), SAV2 s2 => new SAV_SimplePokedex(s2), SAV3 s3 => new SAV_SimplePokedex(s3), SAV4 s4 => new SAV_Pokedex4(s4), SAV5 s5 => new SAV_Pokedex5(s5), SAV6XY xy => new SAV_PokedexXY(xy), SAV6AO ao => new SAV_PokedexORAS(ao), SAV7 s7 => new SAV_PokedexSM(s7), SAV7b b7 => new SAV_PokedexGG(b7), SAV8SWSH swsh => new SAV_PokedexSWSH(swsh), SAV8BS bs => new SAV_PokedexBDSP(bs), SAV8LA la => new SAV_PokedexLA(la), _ => (Form?)null, }; form?.ShowDialog(); } private void B_OpenMiscEditor_Click(object sender, EventArgs e) { using var form = SAV.Generation switch { 3 => new SAV_Misc3(SAV), 4 => new SAV_Misc4((SAV4) SAV), 5 => new SAV_Misc5(SAV), 8 when SAV is SAV8BS bs => new SAV_Misc8b(bs), _ => (Form?)null, }; form?.ShowDialog(); } private void B_OpenRTCEditor_Click(object sender, EventArgs e) { switch (SAV.Generation) { case 2: var sav2 = ((SAV2) SAV); var msg = MsgSaveGen2RTCResetBitflag; if (!sav2.Japanese) // show Reset Key for non-Japanese saves msg = string.Format(MsgSaveGen2RTCResetPassword, sav2.ResetKey) + Environment.NewLine + Environment.NewLine + msg; var dr = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, msg); if (dr == DialogResult.Yes) sav2.ResetRTC(); break; case 3: OpenDialog(new SAV_RTC3(SAV)); break; } } private void B_OUTPasserby_Click(object sender, EventArgs e) { if (SAV.Generation != 6) return; if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgSaveGen6Passerby)) return; var result = PSS6.GetPSSParse((SAV6)SAV); WinFormsUtil.SetClipboardText(string.Join(Environment.NewLine, result)); } private void B_OUTHallofFame_Click(object sender, EventArgs e) { using var form = SAV switch { SAV6 s6 => new SAV_HallOfFame(s6), SAV7 s7 => new SAV_HallOfFame7(s7), _ => (Form?)null, }; form?.ShowDialog(); } private void B_JPEG_Click(object sender, EventArgs e) { var s6 = (SAV6)SAV; byte[] jpeg = s6.GetJPEGData(); if (jpeg.Length == 0) { WinFormsUtil.Alert(MsgSaveJPEGExportFail); return; } string filename = $"{s6.JPEGTitle}'s picture"; using var sfd = new SaveFileDialog { FileName = filename, Filter = "JPEG|*.jpeg" }; if (sfd.ShowDialog() != DialogResult.OK) return; File.WriteAllBytes(sfd.FileName, jpeg); } private void ClickVerifyCHK(object sender, EventArgs e) { if (SAV.State.Edited) { WinFormsUtil.Alert(MsgSaveChecksumFailEdited); return; } if (SAV.ChecksumsValid) { WinFormsUtil.Alert(MsgSaveChecksumValid); return; } if (DialogResult.Yes == WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgSaveChecksumFailExport)) WinFormsUtil.SetClipboardText(SAV.ChecksumInfo); } private void ClickVerifyStoredEntities(object sender, EventArgs e) { var bulk = new BulkAnalysis(SAV, Main.Settings.Bulk); if (bulk.Valid) { WinFormsUtil.Alert("Clean!"); return; } if (WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgClipboardLegalityExport) != DialogResult.Yes) return; var lines = bulk.Parse.Select(z => $"{z.Judgement}: {z.Comment}"); var msg = string.Join(Environment.NewLine, lines); WinFormsUtil.SetClipboardText(msg); SystemSounds.Asterisk.Play(); } // File I/O public bool GetBulkImportSettings(out bool clearAll, out bool overwrite, out PKMImportSetting noSetb) { clearAll = false; noSetb = PKMImportSetting.UseDefault; overwrite = false; var dr = WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, MsgSaveBoxImportClear, MsgSaveBoxImportClearNo); if (dr == DialogResult.Cancel) return false; clearAll = dr == DialogResult.Yes; noSetb = GetPKMSetOverride(ModifyPKM); return true; } private static bool IsFolderPath(out string path) { if (Clipboard.ContainsText()) { var directory = Clipboard.GetText(); // Ask user if they want to use clipboard directory before showing folder browser if (Directory.Exists(directory) && WinFormsUtil.Prompt(MessageBoxButtons.YesNo, string.Format(MsgSaveBoxUseClipboard, directory)) == DialogResult.Yes) { path = directory; return true; } } using var fbd = new FolderBrowserDialog(); var result = fbd.ShowDialog() == DialogResult.OK; path = fbd.SelectedPath; return result; } public bool ExportSaveFile() { ValidateChildren(); bool reload = SAV is SAV7b b && b.FixPreWrite(); if (reload) ReloadSlots(); return WinFormsUtil.ExportSAVDialog(SAV, SAV.CurrentBox); } public bool ExportBackup() { if (!SAV.State.Exportable || SAV.Metadata.FilePath is not { } file) return false; if (!File.Exists(file)) { WinFormsUtil.Error(MsgSaveBackupNotFound, file); return false; } var suggestion = Util.CleanFileName(SAV.Metadata.BAKName); using var sfd = new SaveFileDialog {FileName = suggestion}; if (sfd.ShowDialog() != DialogResult.OK) return false; string path = sfd.FileName; if (!File.Exists(file)) // did they move it again? { WinFormsUtil.Error(MsgSaveBackupNotFound, file); return false; } File.Copy(file, path, true); WinFormsUtil.Alert(MsgSaveBackup, path); return true; } public bool OpenPCBoxBin(byte[] input, out string c) { if (SAV.GetPCBinary().Length == input.Length) { if (SAV.IsAnySlotLockedInBox(0, SAV.BoxCount - 1)) { c = MsgSaveBoxImportPCFailBattle; return false; } if (!SAV.SetPCBinary(input)) { c = string.Format(MsgSaveCurrentGeneration, SAV.Generation); return false; } c = MsgSaveBoxImportPCBinary; } else if (SAV.GetBoxBinary(Box.CurrentBox).Length == input.Length) { if (SAV.IsAnySlotLockedInBox(Box.CurrentBox, Box.CurrentBox)) { c = MsgSaveBoxImportBoxFailBattle; return false; } if (!SAV.SetBoxBinary(input, Box.CurrentBox)) { c = string.Format(MsgSaveCurrentGeneration, SAV.Generation); return false; } c = MsgSaveBoxImportBoxBinary; } else { c = string.Format(MsgSaveCurrentGeneration, SAV.Generation); return false; } SetPKMBoxes(); UpdateBoxViewers(); return true; } public bool OpenGroup(IPokeGroup b, out string c) { var msg = string.Format(MsgSaveBoxImportGroup, Box.CurrentBoxName); var prompt = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, msg, MsgSaveBoxImportOverwrite); if (prompt != DialogResult.Yes) { c = string.Empty; return false; } var noSetb = GetPKMSetOverride(ModifyPKM); var slotSkipped = ImportGroup(b.Contents, SAV, Box.CurrentBox, noSetb); SetPKMBoxes(); UpdateBoxViewers(); c = slotSkipped > 0 ? string.Format(MsgSaveBoxImportSkippedLocked, slotSkipped) : MsgSaveBoxImportGroupSuccess; return true; } private static int ImportGroup(IEnumerable data, SaveFile sav, int box, PKMImportSetting noSetb) { var type = sav.PKMType; int slotSkipped = 0; int index = 0; foreach (var x in data) { var i = index++; if (sav.IsSlotOverwriteProtected(box, i)) { slotSkipped++; continue; } var convert = EntityConverter.ConvertToType(x, type, out _); if (convert?.GetType() != type) { slotSkipped++; continue; } sav.SetBoxSlotAtIndex(x, box, i, noSetb); } return slotSkipped; } public bool DumpBoxes(out string result, string? path = null, bool separate = false) { if (path == null && !IsFolderPath(out path)) { result = path; return false; } Directory.CreateDirectory(path); var count = SAV.DumpBoxes(path, separate); if (count < 0) result = MsgSaveBoxExportInvalid; else result = string.Format(MsgSaveBoxExportPathCount, count) + Environment.NewLine + path; return true; } public bool DumpBox(out string result, string? path = null) { if (path == null && !IsFolderPath(out path)) { result = path; return false; } Directory.CreateDirectory(path); var count = SAV.DumpBox(path, Box.CurrentBox); if (count < 0) result = MsgSaveBoxExportInvalid; else result = string.Format(MsgSaveBoxExportPathCount, count) + Environment.NewLine + path; return true; } public bool LoadBoxes(out string result, string? path = null) { result = string.Empty; if (!SAV.HasBox) return false; if (path == null && !IsFolderPath(out path)) { result = path; return false; } if (!Directory.Exists(path)) return false; if (!GetBulkImportSettings(out bool clearAll, out var overwrite, out var noSetb)) return false; SAV.LoadBoxes(path, out result, Box.CurrentBox, clearAll, overwrite, noSetb); SetPKMBoxes(); UpdateBoxViewers(); return true; } public bool ToggleInterface() { FieldsLoaded = false; ToggleViewReset(); ToggleViewSubEditors(SAV); bool WindowTranslationRequired = false; WindowTranslationRequired |= ToggleViewBox(SAV); int BoxTab = tabBoxMulti.TabPages.IndexOf(Tab_Box); WindowTranslationRequired |= ToggleViewParty(SAV, BoxTab); int PartyTab = tabBoxMulti.TabPages.IndexOf(Tab_PartyBattle); WindowTranslationRequired |= ToggleViewDaycare(SAV, BoxTab, PartyTab); SetPKMBoxes(); // Reload all of the PKX Windows ToggleViewMisc(SAV); FieldsLoaded = true; return WindowTranslationRequired; } private void ToggleViewReset() { // Close subforms that are save dependent foreach (var z in M.Boxes.Skip(1).ToArray()) z.FindForm()?.Close(); Box.M = M; SL_Party.M = M; if (SAV.HasBox) { bool newSlots = Box.InitializeFromSAV(SAV); if (newSlots) { Box.HorizontallyCenter(Tab_Box); foreach (var pb in Box.SlotPictureBoxes) pb.ContextMenuStrip = menu.mnuVSD; var grid = Box.BoxPokeGrid; var height = grid.Height + grid.Location.Y + Box.Location.Y; // needed height var required = height + 16; var allowed = Tab_Box.Height; if (required > allowed) { var form = FindForm(); if (form != null) form.Height += required - allowed; } } } if (SAV.HasParty) { bool newSlots = SL_Party.InitializeFromSAV(SAV); if (newSlots) { SL_Party.HorizontallyCenter(Tab_PartyBattle); foreach (var pb in SL_Party.SlotPictureBoxes) pb.ContextMenuStrip = menu.mnuVSD; } } SortMenu.ToggleVisibility(); } private bool ToggleViewBox(SaveFile sav) { if (!sav.HasBox) { if (tabBoxMulti.TabPages.Contains(Tab_Box)) tabBoxMulti.TabPages.Remove(Tab_Box); B_SaveBoxBin.Enabled = false; return false; } B_SaveBoxBin.Enabled = true; tabBoxMulti.SelectedIndex = 0; var box = sav.CurrentBox; Box.CurrentBox = (uint)box >= sav.BoxCount ? 0 : box; if (tabBoxMulti.TabPages.Contains(Tab_Box)) return false; tabBoxMulti.TabPages.Insert(0, Tab_Box); return true; } private bool ToggleViewParty(SaveFile sav, int BoxTab) { if (!sav.HasParty || !sav.State.Exportable) { if (tabBoxMulti.TabPages.Contains(Tab_PartyBattle)) tabBoxMulti.TabPages.Remove(Tab_PartyBattle); return false; } if (tabBoxMulti.TabPages.Contains(Tab_PartyBattle)) return false; int index = BoxTab; if (index < 0) index = -1; tabBoxMulti.TabPages.Insert(index + 1, Tab_PartyBattle); return true; } private bool ToggleViewDaycare(SaveFile sav, int BoxTab, int PartyTab) { if ((!sav.HasDaycare && SL_Extra.SlotCount == 0) || !sav.State.Exportable) { if (tabBoxMulti.TabPages.Contains(Tab_Other)) tabBoxMulti.TabPages.Remove(Tab_Other); return false; } SlotPictureBoxes[1].Visible = sav.Generation >= 2; // Second daycare slot if (tabBoxMulti.TabPages.Contains(Tab_Other)) return false; int index = PartyTab; if (index < 0) index = BoxTab; if (index < 0) index = -1; tabBoxMulti.TabPages.Insert(index + 1, Tab_Other); return true; } private void ToggleViewSubEditors(SaveFile sav) { if (!sav.State.Exportable || sav is BulkStorage) { GB_SAVtools.Visible = false; B_JPEG.Visible = false; SL_Extra.HideAllSlots(); return; } GB_Daycare.Visible = sav.HasDaycare; B_OpenPokeblocks.Visible = sav is SAV6AO; B_OpenSecretBase.Visible = sav is SAV6AO; B_OpenPokepuffs.Visible = sav is ISaveBlock6Main; B_JPEG.Visible = B_OpenLinkInfo.Visible = B_OpenSuperTraining.Visible = B_OUTPasserby.Visible = sav is ISaveBlock6Main; B_OpenBoxLayout.Visible = sav.HasNamableBoxes; B_OpenWondercards.Visible = sav.HasWondercards; B_OpenHallofFame.Visible = sav is ISaveBlock6Main or SAV7; B_OpenOPowers.Visible = sav is ISaveBlock6Main; B_OpenPokedex.Visible = sav.HasPokeDex; B_OpenBerryField.Visible = sav is SAV6XY; // oras undocumented B_OpenFriendSafari.Visible = sav is SAV6XY; B_OpenEventFlags.Visible = sav is IEventFlag37 or SAV1 or SAV2 or SAV8BS or SAV7b; B_CGearSkin.Visible = sav.Generation == 5; B_OpenPokeBeans.Visible = B_CellsStickers.Visible = B_FestivalPlaza.Visible = sav is SAV7; B_OtherSlots.Visible = sav is SAV1StadiumJ or SAV1Stadium or SAV2Stadium; B_OpenTrainerInfo.Visible = B_OpenItemPouch.Visible = (sav.HasParty && SAV is not SAV4BR) || SAV is SAV7b; // Box RS & Battle Revolution B_OpenMiscEditor.Visible = sav is SAV3 or SAV4 or SAV5 or SAV8BS; B_Roamer.Visible = sav is SAV3; B_OpenHoneyTreeEditor.Visible = sav is SAV4Sinnoh; B_OpenUGSEditor.Visible = sav is SAV4Sinnoh or SAV8BS; B_OpenSealStickers.Visible = B_Poffins.Visible = sav is SAV8BS; B_OpenApricorn.Visible = sav is SAV4HGSS; B_OpenRTCEditor.Visible = sav.Generation == 2 || sav is IGen3Hoenn; B_MailBox.Visible = sav is SAV2 or SAV3 or SAV4 or SAV5; B_Raids.Visible = sav is SAV8SWSH; B_RaidArmor.Visible = sav is SAV8SWSH {SaveRevision: >= 1}; B_RaidCrown.Visible = sav is SAV8SWSH {SaveRevision: >= 2}; GB_SAVtools.Visible = B_Blocks.Visible = true; var list = FLP_SAVtools.Controls.OfType().OrderBy(z => z.Text).ToArray(); FLP_SAVtools.Controls.Clear(); FLP_SAVtools.Controls.AddRange(list); SL_Extra.SAV = sav; SL_Extra.Initialize(sav.GetExtraSlots(HaX), InitializeDragDrop); } private void ToggleViewMisc(SaveFile sav) { // Generational Interface ToggleSecrets(sav, HideSecretDetails); B_VerifyCHK.Visible = SAV.State.Exportable; Menu_ExportBAK.Visible = SAV.State.Exportable && SAV.Metadata.FilePath is not null; if (sav is SAV4BR br) { L_SaveSlot.Visible = CB_SaveSlot.Visible = true; var current = br.CurrentSlot; var list = br.SaveNames.Select((z, i) => new ComboItem(z, i)).ToList(); CB_SaveSlot.InitializeBinding(); CB_SaveSlot.DataSource = new BindingSource(list, null); CB_SaveSlot.SelectedValue = current; } else { L_SaveSlot.Visible = CB_SaveSlot.Visible = false; } if (sav is ISecureValueStorage s) { TB_Secure1.Text = s.TimeStampCurrent.ToString("X16"); TB_Secure2.Text = s.TimeStampPrevious.ToString("X16"); } if (sav is IGameSync sync) { var gsid = sync.GameSyncID; TB_GameSync.Enabled = !string.IsNullOrEmpty(gsid); TB_GameSync.MaxLength = sync.GameSyncIDSize; TB_GameSync.Text = (string.IsNullOrEmpty(gsid) ? 0.ToString() : gsid).PadLeft(sync.GameSyncIDSize, '0'); } } private void ToggleSecrets(SaveFile sav, bool hide) { var shouldShow = sav.State.Exportable && !hide; TB_Secure1.Visible = TB_Secure2.Visible = L_Secure1.Visible = L_Secure2.Visible = shouldShow && sav is ISecureValueStorage; TB_GameSync.Visible = L_GameSync.Visible = shouldShow && sav is IGameSync; } // DragDrop private void MultiDragOver(object sender, DragEventArgs e) { // iterate over all tabs to see if a tab switch should occur when drag/dropping Point pt = tabBoxMulti.PointToClient(new Point(e.X, e.Y)); for (int i = 0; i < tabBoxMulti.TabCount; i++) { if (tabBoxMulti.SelectedIndex == i || !tabBoxMulti.GetTabRect(i).Contains(pt)) continue; tabBoxMulti.SelectedIndex = i; return; } } public void ClickShowdownExportParty(object sender, EventArgs e) => ExportShowdownText(SAV, MsgSimulatorExportParty, sav => sav.PartyData); public void ClickShowdownExportCurrentBox(object sender, EventArgs e) { if (!SAV.HasBox) return; ExportShowdownText(SAV, MsgSimulatorExportList, sav => (ModifierKeys & Keys.Control) != 0 ? sav.BoxData : sav.GetBoxData(CurrentBox)); } private static void ExportShowdownText(SaveFile sav, string success, Func> fetch) { var list = fetch(sav); var result = ShowdownParsing.GetShowdownSets(list, Environment.NewLine + Environment.NewLine); if (string.IsNullOrWhiteSpace(result)) return; if (WinFormsUtil.SetClipboardText(result)) WinFormsUtil.Alert(success); } private void B_OpenUGSEditor_Click(object sender, EventArgs e) { Form form; if (SAV is SAV4Sinnoh s) form = new SAV_Underground(s); else if (SAV is SAV8BS bs) form = new SAV_Underground8b(bs); else return; form.ShowDialog(); form.Dispose(); } private void B_OpenSealStickers_Click(object sender, EventArgs e) { if (SAV is not SAV8BS bs) return; using var form = new SAV_SealStickers8b(bs); form.ShowDialog(); } private void B_Poffins_Click(object sender, EventArgs e) { if (SAV is not SAV8BS bs) return; using var form = new SAV_Poffin8b(bs); form.ShowDialog(); } private void B_FestivalPlaza_Click(object sender, EventArgs e) { if (SAV is not SAV7 s) return; using var form = new SAV_FestivalPlaza(s); form.ShowDialog(); } private void B_MailBox_Click(object sender, EventArgs e) { using var form = new SAV_MailBox(SAV); form.ShowDialog(); ResetParty(); } private static PKMImportSetting GetPKMSetOverride(bool currentSetting) { var yn = currentSetting ? MsgYes : MsgNo; var choice = WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, MsgSaveBoxImportModifyIntro, MsgSaveBoxImportModifyYes + Environment.NewLine + MsgSaveBoxImportModifyNo + Environment.NewLine + string.Format(MsgSaveBoxImportModifyCurrent, yn)); return choice switch { DialogResult.Yes => PKMImportSetting.Update, DialogResult.No => PKMImportSetting.Skip, _ => PKMImportSetting.UseDefault, }; } private void Menu_ExportBAK_Click(object sender, EventArgs e) => ExportBackup(); }