2017-05-12 04:34:18 +00:00
|
|
|
|
using PKHeX.Core;
|
|
|
|
|
using System;
|
2016-07-09 22:30:12 +00:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Drawing;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
2018-08-04 17:06:06 +00:00
|
|
|
|
using System.Reflection;
|
2019-11-23 05:23:00 +00:00
|
|
|
|
using System.Runtime.InteropServices;
|
2016-07-09 22:30:12 +00:00
|
|
|
|
using System.Windows.Forms;
|
|
|
|
|
|
2018-04-07 04:23:09 +00:00
|
|
|
|
using static PKHeX.Core.MessageStrings;
|
2019-11-23 05:23:00 +00:00
|
|
|
|
using Exception = System.Exception;
|
2018-04-07 04:23:09 +00:00
|
|
|
|
|
2017-01-08 07:54:09 +00:00
|
|
|
|
namespace PKHeX.WinForms
|
2016-07-09 22:30:12 +00:00
|
|
|
|
{
|
2017-01-08 07:54:09 +00:00
|
|
|
|
public static class WinFormsUtil
|
2016-07-09 22:30:12 +00:00
|
|
|
|
{
|
2018-03-21 05:34:44 +00:00
|
|
|
|
internal static void TranslateInterface(Control form, string lang) => form.TranslateInterface(lang);
|
2017-10-01 17:50:00 +00:00
|
|
|
|
|
2019-10-03 03:04:12 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Centers the <see cref="child"/> horizontally and vertically so that its center is the same as the <see cref="parent"/>'s center.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="child"></param>
|
|
|
|
|
/// <param name="parent"></param>
|
2020-10-18 18:02:39 +00:00
|
|
|
|
internal static void CenterToForm(this Control child, Control? parent)
|
2016-07-09 22:30:12 +00:00
|
|
|
|
{
|
2020-10-18 18:02:39 +00:00
|
|
|
|
if (parent == null)
|
|
|
|
|
return;
|
2018-07-22 02:20:11 +00:00
|
|
|
|
int x = parent.Location.X + ((parent.Width - child.Width) / 2);
|
|
|
|
|
int y = parent.Location.Y + ((parent.Height - child.Height) / 2);
|
2016-07-09 22:30:12 +00:00
|
|
|
|
child.Location = new Point(Math.Max(x, 0), Math.Max(y, 0));
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-03 03:04:12 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Horizontally centers the <see cref="child"/> to the <see cref="parent"/>'s horizontal center.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal static void HorizontallyCenter(this Control child, Control parent)
|
|
|
|
|
{
|
|
|
|
|
int x = ((parent.Width - child.Width) / 2);
|
|
|
|
|
child.Location = new Point(x, child.Location.Y);
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-18 18:02:39 +00:00
|
|
|
|
public static T? FirstFormOfType<T>() where T : Form => (T?)Application.OpenForms.Cast<Form>().FirstOrDefault(form => form is T);
|
2018-07-22 02:20:11 +00:00
|
|
|
|
|
2020-10-18 18:02:39 +00:00
|
|
|
|
public static T? FindFirstControlOfType<T>(Control aParent) where T : class
|
2018-05-05 15:07:22 +00:00
|
|
|
|
{
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
2019-05-13 22:53:27 +00:00
|
|
|
|
if (aParent is T t)
|
2018-05-05 15:07:22 +00:00
|
|
|
|
return t;
|
|
|
|
|
|
|
|
|
|
if (aParent.Parent != null)
|
|
|
|
|
aParent = aParent.Parent;
|
|
|
|
|
else
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-07-22 02:20:11 +00:00
|
|
|
|
|
2020-10-18 18:02:39 +00:00
|
|
|
|
public static T? GetUnderlyingControl<T>(object sender) where T : class
|
2019-09-25 01:14:15 +00:00
|
|
|
|
{
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
switch (sender)
|
|
|
|
|
{
|
2021-06-24 07:36:04 +00:00
|
|
|
|
case T p:
|
|
|
|
|
return p;
|
2019-09-25 01:14:15 +00:00
|
|
|
|
case ToolStripItem t:
|
|
|
|
|
sender = t.Owner;
|
|
|
|
|
continue;
|
|
|
|
|
case ContextMenuStrip c:
|
|
|
|
|
sender = c.SourceControl;
|
|
|
|
|
continue;
|
|
|
|
|
default:
|
|
|
|
|
return default;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-05-23 04:55:05 +00:00
|
|
|
|
|
2019-10-08 01:40:09 +00:00
|
|
|
|
public static bool OpenWindowExists<T>(this Form parent) where T : Form
|
|
|
|
|
{
|
|
|
|
|
var form = FirstFormOfType<T>();
|
|
|
|
|
if (form == null)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
form.CenterToForm(parent);
|
|
|
|
|
form.BringToFront();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-26 03:59:29 +00:00
|
|
|
|
#region Message Displays
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Displays a dialog showing the details of an error.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="friendlyMessage">User-friendly message about the error.</param>
|
|
|
|
|
/// <param name="exception">Instance of the error's <see cref="Exception"/>.</param>
|
|
|
|
|
/// <returns>The <see cref="DialogResult"/> associated with the dialog.</returns>
|
|
|
|
|
internal static DialogResult Error(string friendlyMessage, Exception exception)
|
|
|
|
|
{
|
|
|
|
|
System.Media.SystemSounds.Exclamation.Play();
|
2016-08-26 04:01:42 +00:00
|
|
|
|
return ErrorWindow.ShowErrorDialog(friendlyMessage, exception, true);
|
2016-08-26 03:59:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Displays a dialog showing the details of an error.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="lines">User-friendly message about the error.</param>
|
|
|
|
|
/// <returns>The <see cref="DialogResult"/> associated with the dialog.</returns>
|
2016-07-09 22:30:12 +00:00
|
|
|
|
internal static DialogResult Error(params string[] lines)
|
|
|
|
|
{
|
2019-02-12 06:39:12 +00:00
|
|
|
|
System.Media.SystemSounds.Hand.Play();
|
2016-07-09 22:30:12 +00:00
|
|
|
|
string msg = string.Join(Environment.NewLine + Environment.NewLine, lines);
|
|
|
|
|
return MessageBox.Show(msg, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
|
|
|
}
|
2016-08-26 03:59:29 +00:00
|
|
|
|
|
2020-01-03 23:29:12 +00:00
|
|
|
|
internal static DialogResult Alert(params string[] lines) => Alert(true, lines);
|
|
|
|
|
|
|
|
|
|
internal static DialogResult Alert(bool sound, params string[] lines)
|
2016-07-09 22:30:12 +00:00
|
|
|
|
{
|
2020-01-03 23:29:12 +00:00
|
|
|
|
if (sound)
|
|
|
|
|
System.Media.SystemSounds.Asterisk.Play();
|
2016-07-09 22:30:12 +00:00
|
|
|
|
string msg = string.Join(Environment.NewLine + Environment.NewLine, lines);
|
2020-01-03 23:29:12 +00:00
|
|
|
|
return MessageBox.Show(msg, "Alert", MessageBoxButtons.OK, sound ? MessageBoxIcon.Information : MessageBoxIcon.None);
|
2016-07-09 22:30:12 +00:00
|
|
|
|
}
|
2016-08-26 03:59:29 +00:00
|
|
|
|
|
2016-07-09 22:30:12 +00:00
|
|
|
|
internal static DialogResult Prompt(MessageBoxButtons btn, params string[] lines)
|
|
|
|
|
{
|
2020-01-03 23:29:12 +00:00
|
|
|
|
System.Media.SystemSounds.Asterisk.Play();
|
2016-07-09 22:30:12 +00:00
|
|
|
|
string msg = string.Join(Environment.NewLine + Environment.NewLine, lines);
|
2020-01-03 23:29:12 +00:00
|
|
|
|
return MessageBox.Show(msg, "Prompt", btn, MessageBoxIcon.Question);
|
2016-07-09 22:30:12 +00:00
|
|
|
|
}
|
2018-07-14 23:00:28 +00:00
|
|
|
|
#endregion
|
2016-07-09 22:30:12 +00:00
|
|
|
|
|
2019-11-23 05:23:00 +00:00
|
|
|
|
internal static bool SetClipboardText(string text)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
Clipboard.SetText(text);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
catch (ExternalException x)
|
|
|
|
|
{
|
|
|
|
|
Error(MsgClipboardFailWrite, x);
|
|
|
|
|
}
|
2020-09-19 05:11:13 +00:00
|
|
|
|
// Clipboard might be locked sometimes
|
2019-11-23 05:23:00 +00:00
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
Error(MsgClipboardFailWrite);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-22 02:20:11 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the selected value of the input <see cref="cb"/>. If no value is selected, will return 0.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="cb">ComboBox to retrieve value for.</param>
|
2019-07-14 22:06:45 +00:00
|
|
|
|
internal static int GetIndex(ListControl cb)
|
2016-07-09 22:30:12 +00:00
|
|
|
|
{
|
2020-10-18 18:02:39 +00:00
|
|
|
|
return (int)(cb.SelectedValue ?? 0);
|
2016-07-09 22:30:12 +00:00
|
|
|
|
}
|
2016-07-29 23:00:56 +00:00
|
|
|
|
|
2020-11-14 16:20:48 +00:00
|
|
|
|
public static void PanelScroll(object? sender, ScrollEventArgs e)
|
2016-07-29 23:00:56 +00:00
|
|
|
|
{
|
2020-12-23 05:24:41 +00:00
|
|
|
|
if (sender is not ScrollableControl p || e.NewValue < 0)
|
2017-03-09 07:32:52 +00:00
|
|
|
|
return;
|
2016-07-29 23:00:56 +00:00
|
|
|
|
switch (e.ScrollOrientation)
|
|
|
|
|
{
|
|
|
|
|
case ScrollOrientation.HorizontalScroll:
|
2018-10-11 22:44:36 +00:00
|
|
|
|
p.HorizontalScroll.Value = Clamp(e.NewValue, p.HorizontalScroll);
|
2016-07-29 23:00:56 +00:00
|
|
|
|
break;
|
|
|
|
|
case ScrollOrientation.VerticalScroll:
|
2018-10-11 22:44:36 +00:00
|
|
|
|
p.VerticalScroll.Value = Clamp(e.NewValue, p.VerticalScroll);
|
2016-07-29 23:00:56 +00:00
|
|
|
|
break;
|
2019-10-08 01:40:09 +00:00
|
|
|
|
default:
|
2020-11-14 16:20:48 +00:00
|
|
|
|
throw new IndexOutOfRangeException(nameof(e.ScrollOrientation));
|
2016-07-29 23:00:56 +00:00
|
|
|
|
}
|
2019-10-08 01:40:09 +00:00
|
|
|
|
static int Clamp(int value, ScrollProperties prop) => Math.Max(prop.Minimum, Math.Min(prop.Maximum, value));
|
2016-07-29 23:00:56 +00:00
|
|
|
|
}
|
2018-07-14 23:00:28 +00:00
|
|
|
|
|
2018-08-04 17:06:06 +00:00
|
|
|
|
public static void DoubleBuffered(this DataGridView dgv, bool setting)
|
|
|
|
|
{
|
|
|
|
|
Type dgvType = dgv.GetType();
|
2020-10-18 18:02:39 +00:00
|
|
|
|
var pi = dgvType.GetProperty("DoubleBuffered", BindingFlags.Instance | BindingFlags.NonPublic);
|
|
|
|
|
if (pi == null)
|
|
|
|
|
throw new Exception(nameof(dgv));
|
2018-08-04 17:06:06 +00:00
|
|
|
|
pi.SetValue(dgv, setting, null);
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-14 23:00:28 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Initializes the <see cref="control"/> to be bound to a provided <see cref="ComboItem"/> list.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="control">Control to initialize binding</param>
|
|
|
|
|
public static void InitializeBinding(this ListControl control)
|
|
|
|
|
{
|
|
|
|
|
control.DisplayMember = nameof(ComboItem.Text);
|
|
|
|
|
control.ValueMember = nameof(ComboItem.Value);
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-14 16:20:48 +00:00
|
|
|
|
public static void RemoveDropCB(object? sender, KeyEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
if (sender == null)
|
|
|
|
|
return;
|
|
|
|
|
((ComboBox)sender).DroppedDown = false;
|
|
|
|
|
}
|
2018-07-22 02:20:11 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Iterates the Control's child controls recursively to obtain all controls of the specified type.
|
|
|
|
|
/// </summary>
|
2019-01-05 23:40:25 +00:00
|
|
|
|
/// <typeparam name="T">Type of control</typeparam>
|
2018-07-22 02:20:11 +00:00
|
|
|
|
/// <param name="control"></param>
|
2019-01-05 23:40:25 +00:00
|
|
|
|
/// <returns>All children and subchildren contained by <see cref="control"/>.</returns>
|
|
|
|
|
public static IEnumerable<Control> GetAllControlsOfType<T>(Control control) where T : Control
|
2017-06-18 04:49:14 +00:00
|
|
|
|
{
|
2019-01-05 23:40:25 +00:00
|
|
|
|
foreach (var c in control.Controls.Cast<Control>())
|
|
|
|
|
{
|
|
|
|
|
if (c is T match)
|
|
|
|
|
yield return match;
|
|
|
|
|
foreach (var sub in GetAllControlsOfType<T>(c))
|
|
|
|
|
yield return sub;
|
|
|
|
|
}
|
2017-06-18 04:49:14 +00:00
|
|
|
|
}
|
2017-01-08 07:54:09 +00:00
|
|
|
|
|
2018-07-22 02:20:11 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Reads in custom extension types that allow the program to open more extensions.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="exts">Extensions to add</param>
|
2019-07-14 22:06:45 +00:00
|
|
|
|
public static void AddSaveFileExtensions(IEnumerable<string> exts)
|
|
|
|
|
{
|
|
|
|
|
// Only add new (unique) extensions
|
2021-05-20 22:03:15 +00:00
|
|
|
|
var dest = CustomSaveExtensions;
|
|
|
|
|
foreach (var ext in exts)
|
|
|
|
|
{
|
|
|
|
|
if (!dest.Contains(ext))
|
|
|
|
|
dest.Add(ext);
|
|
|
|
|
}
|
2019-07-14 22:06:45 +00:00
|
|
|
|
}
|
2018-07-22 02:20:11 +00:00
|
|
|
|
|
2020-12-22 07:37:07 +00:00
|
|
|
|
private static readonly List<string> CustomSaveExtensions = new()
|
2018-05-22 02:12:02 +00:00
|
|
|
|
{
|
|
|
|
|
// THESE ARE SAVE FILE EXTENSION TYPES. SAVE STATE (RAM SNAPSHOT) EXTENSIONS DO NOT GO HERE.
|
|
|
|
|
"sav", // standard
|
|
|
|
|
"dat", // VC data
|
|
|
|
|
"gci", // Dolphin GameCubeImage
|
|
|
|
|
"dsv", // DeSmuME
|
|
|
|
|
"srm", // RetroArch save files
|
|
|
|
|
"fla", // flashcard
|
|
|
|
|
"SaveRAM", // BizHawk
|
|
|
|
|
};
|
2018-07-22 02:20:11 +00:00
|
|
|
|
|
2019-05-13 22:58:36 +00:00
|
|
|
|
public static bool IsFileExtensionSAV(string file) => CustomSaveExtensions.Contains(Path.GetExtension(file));
|
|
|
|
|
|
2018-05-22 02:12:02 +00:00
|
|
|
|
private static string ExtraSaveExtensions => ";" + string.Join(";", CustomSaveExtensions.Select(z => $"*.{z}"));
|
|
|
|
|
|
2019-02-23 07:04:10 +00:00
|
|
|
|
public static bool DetectSaveFileOnFileOpen { private get; set; } = true;
|
|
|
|
|
|
2017-05-18 05:00:06 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Opens a dialog to open a <see cref="SaveFile"/>, <see cref="PKM"/> file, or any other supported file.
|
|
|
|
|
/// </summary>
|
2019-02-17 03:53:14 +00:00
|
|
|
|
/// <param name="extensions">Misc extensions of <see cref="PKM"/> files supported by the Save File.</param>
|
2017-05-18 05:00:06 +00:00
|
|
|
|
/// <param name="path">Output result path</param>
|
|
|
|
|
/// <returns>Result of whether or not a file is to be loaded from the output path.</returns>
|
2020-10-18 18:02:39 +00:00
|
|
|
|
public static bool OpenSAVPKMDialog(IEnumerable<string> extensions, out string? path)
|
2017-05-18 05:00:06 +00:00
|
|
|
|
{
|
2019-02-17 03:53:14 +00:00
|
|
|
|
string supported = string.Join(";", extensions.Select(s => $"*.{s}").Concat(new[] { "*.pkm" }));
|
2019-10-08 01:40:09 +00:00
|
|
|
|
using var ofd = new OpenFileDialog
|
2017-05-18 05:00:06 +00:00
|
|
|
|
{
|
|
|
|
|
Filter = "All Files|*.*" +
|
2018-05-22 02:12:02 +00:00
|
|
|
|
$"|Supported Files (*.*)|main;*.bin;{supported};*.bak" + ExtraSaveExtensions +
|
|
|
|
|
"|Save Files (*.sav)|main" + ExtraSaveExtensions +
|
|
|
|
|
"|Decrypted PKM File (*.pkm)|" + supported +
|
2017-05-18 05:00:06 +00:00
|
|
|
|
"|Binary File|*.bin" +
|
2021-08-20 20:49:20 +00:00
|
|
|
|
"|Backup File|*.bak",
|
2017-05-18 05:00:06 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Detect main
|
2020-10-18 18:02:39 +00:00
|
|
|
|
SaveFile? sav = null;
|
2019-02-23 07:04:10 +00:00
|
|
|
|
if (DetectSaveFileOnFileOpen)
|
2021-10-22 05:13:21 +00:00
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
sav = SaveFinder.FindMostRecentSaveFile();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Error(ex.Message);
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-07-15 20:35:58 +00:00
|
|
|
|
|
|
|
|
|
if (sav != null)
|
2020-12-05 13:36:23 +00:00
|
|
|
|
ofd.FileName = sav.Metadata.FileName;
|
2017-05-18 05:00:06 +00:00
|
|
|
|
|
|
|
|
|
if (ofd.ShowDialog() != DialogResult.OK)
|
2018-07-15 20:35:58 +00:00
|
|
|
|
{
|
|
|
|
|
path = null;
|
2017-05-18 05:00:06 +00:00
|
|
|
|
return false;
|
2018-07-15 20:35:58 +00:00
|
|
|
|
}
|
2017-05-18 05:00:06 +00:00
|
|
|
|
|
|
|
|
|
path = ofd.FileName;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2018-07-22 02:20:11 +00:00
|
|
|
|
|
2017-05-18 05:00:06 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Opens a dialog to save a <see cref="PKM"/> file.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="pk"><see cref="PKM"/> file to be saved.</param>
|
|
|
|
|
/// <returns>Result of whether or not the file was saved.</returns>
|
|
|
|
|
public static bool SavePKMDialog(PKM pk)
|
|
|
|
|
{
|
|
|
|
|
string pkx = pk.Extension;
|
2019-02-08 05:40:20 +00:00
|
|
|
|
bool allowEncrypted = pk.Format >= 3 && pkx[0] == 'p';
|
|
|
|
|
var genericFilter = $"Decrypted PKM File|*.{pkx}" +
|
2021-05-14 22:30:55 +00:00
|
|
|
|
(allowEncrypted ? $"|Encrypted PKM File|*.e{pkx[1..]}" : string.Empty) +
|
2017-05-18 05:00:06 +00:00
|
|
|
|
"|Binary File|*.bin" +
|
2019-02-08 05:40:20 +00:00
|
|
|
|
"|All Files|*.*";
|
2019-10-08 01:40:09 +00:00
|
|
|
|
using var sfd = new SaveFileDialog
|
2019-02-08 05:40:20 +00:00
|
|
|
|
{
|
|
|
|
|
Filter = genericFilter,
|
2017-05-18 05:00:06 +00:00
|
|
|
|
DefaultExt = pkx,
|
2021-08-20 20:49:20 +00:00
|
|
|
|
FileName = Util.CleanFileName(pk.FileName),
|
2017-05-18 05:00:06 +00:00
|
|
|
|
};
|
|
|
|
|
if (sfd.ShowDialog() != DialogResult.OK)
|
|
|
|
|
return false;
|
|
|
|
|
|
2018-02-22 04:35:07 +00:00
|
|
|
|
SavePKM(pk, sfd.FileName, pkx);
|
2017-05-18 05:00:06 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
2018-07-22 02:20:11 +00:00
|
|
|
|
|
2018-02-22 04:35:07 +00:00
|
|
|
|
private static void SavePKM(PKM pk, string path, string pkx)
|
|
|
|
|
{
|
|
|
|
|
SaveBackup(path);
|
|
|
|
|
string ext = Path.GetExtension(path);
|
2019-11-16 22:03:25 +00:00
|
|
|
|
var data = $".{pkx}" == ext ? pk.DecryptedPartyData : pk.EncryptedPartyData;
|
2018-02-22 04:35:07 +00:00
|
|
|
|
File.WriteAllBytes(path, data);
|
|
|
|
|
}
|
2018-07-22 02:20:11 +00:00
|
|
|
|
|
2018-02-22 04:35:07 +00:00
|
|
|
|
private static void SaveBackup(string path)
|
|
|
|
|
{
|
|
|
|
|
if (!File.Exists(path))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// File already exists, save a .bak
|
|
|
|
|
string bakpath = $"{path}.bak";
|
|
|
|
|
if (!File.Exists(bakpath))
|
|
|
|
|
File.Move(path, bakpath);
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-18 05:00:06 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Opens a dialog to save a <see cref="SaveFile"/> file.
|
|
|
|
|
/// </summary>
|
2019-02-17 03:53:14 +00:00
|
|
|
|
/// <param name="sav"><see cref="SaveFile"/> to be saved.</param>
|
2019-07-14 22:06:45 +00:00
|
|
|
|
/// <param name="currentBox">Box the player will be greeted with when accessing the PC ingame.</param>
|
2017-05-18 05:00:06 +00:00
|
|
|
|
/// <returns>Result of whether or not the file was saved.</returns>
|
2019-07-14 22:06:45 +00:00
|
|
|
|
public static bool ExportSAVDialog(SaveFile sav, int currentBox = 0)
|
2017-05-18 05:00:06 +00:00
|
|
|
|
{
|
2019-10-08 01:40:09 +00:00
|
|
|
|
using var sfd = new SaveFileDialog
|
2017-05-18 05:00:06 +00:00
|
|
|
|
{
|
2020-12-05 13:36:23 +00:00
|
|
|
|
Filter = sav.Metadata.Filter,
|
|
|
|
|
FileName = sav.Metadata.FileName,
|
2018-05-22 02:12:02 +00:00
|
|
|
|
FilterIndex = 1000, // default to last, All Files
|
2021-08-20 20:49:20 +00:00
|
|
|
|
RestoreDirectory = true,
|
2017-05-18 05:00:06 +00:00
|
|
|
|
};
|
2020-12-05 13:36:23 +00:00
|
|
|
|
if (Directory.Exists(sav.Metadata.FileFolder))
|
|
|
|
|
sfd.InitialDirectory = sav.Metadata.FileFolder;
|
2017-05-18 05:00:06 +00:00
|
|
|
|
|
2019-07-14 22:06:45 +00:00
|
|
|
|
if (sfd.ShowDialog() != DialogResult.OK)
|
2017-05-18 05:00:06 +00:00
|
|
|
|
return false;
|
|
|
|
|
|
2020-10-18 18:02:39 +00:00
|
|
|
|
// Set box now that we're saving
|
2019-02-17 03:53:14 +00:00
|
|
|
|
if (sav.HasBox)
|
2019-07-14 22:06:45 +00:00
|
|
|
|
sav.CurrentBox = currentBox;
|
2017-05-18 05:00:06 +00:00
|
|
|
|
|
2020-10-18 18:02:39 +00:00
|
|
|
|
var path = sfd.FileName;
|
|
|
|
|
if (path == null)
|
2020-11-14 16:20:48 +00:00
|
|
|
|
throw new NullReferenceException(nameof(sfd.FileName));
|
2020-10-18 18:02:39 +00:00
|
|
|
|
|
2019-07-14 22:06:45 +00:00
|
|
|
|
ExportSAV(sav, path);
|
2019-02-17 03:53:14 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void ExportSAV(SaveFile sav, string path)
|
|
|
|
|
{
|
2021-06-24 16:16:36 +00:00
|
|
|
|
var ext = Path.GetExtension(path).ToLowerInvariant();
|
2020-12-08 03:49:04 +00:00
|
|
|
|
var flags = sav.Metadata.GetSuggestedFlags(ext);
|
2019-02-19 05:59:57 +00:00
|
|
|
|
|
2017-05-18 05:00:06 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
2019-02-19 05:59:57 +00:00
|
|
|
|
File.WriteAllBytes(path, sav.Write(flags));
|
2020-12-05 13:36:23 +00:00
|
|
|
|
sav.State.Edited = false;
|
|
|
|
|
sav.Metadata.SetExtraInfo(path);
|
2019-02-17 03:53:14 +00:00
|
|
|
|
Alert(MsgSaveExportSuccessPath, path);
|
2017-05-18 05:00:06 +00:00
|
|
|
|
}
|
|
|
|
|
catch (Exception x)
|
|
|
|
|
{
|
2019-09-11 05:07:50 +00:00
|
|
|
|
switch (x)
|
|
|
|
|
{
|
2020-12-22 07:37:07 +00:00
|
|
|
|
case UnauthorizedAccessException:
|
|
|
|
|
case FileNotFoundException:
|
|
|
|
|
case IOException:
|
2019-09-11 05:07:50 +00:00
|
|
|
|
Error(MsgFileWriteFail + Environment.NewLine + x.Message, MsgFileWriteProtectedAdvice);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
throw;
|
|
|
|
|
}
|
2017-05-18 05:00:06 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-07-22 02:20:11 +00:00
|
|
|
|
|
2017-05-19 01:10:58 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Opens a dialog to save a <see cref="MysteryGift"/> file.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="gift"><see cref="MysteryGift"/> to be saved.</param>
|
2019-02-02 23:20:08 +00:00
|
|
|
|
/// <param name="origin">Game the gift originates from</param>
|
2017-05-19 01:10:58 +00:00
|
|
|
|
/// <returns>Result of whether or not the file was saved.</returns>
|
PKHeX.Core Nullable cleanup (#2401)
* Handle some nullable cases
Refactor MysteryGift into a second abstract class (backed by a byte array, or fake data)
Make some classes have explicit constructors instead of { } initialization
* Handle bits more obviously without null
* Make SaveFile.BAK explicitly readonly again
* merge constructor methods to have readonly fields
* Inline some properties
* More nullable handling
* Rearrange box actions
define straightforward classes to not have any null properties
* Make extrabyte reference array immutable
* Move tooltip creation to designer
* Rearrange some logic to reduce nesting
* Cache generated fonts
* Split mystery gift album purpose
* Handle more tooltips
* Disallow null setters
* Don't capture RNG object, only type enum
* Unify learnset objects
Now have readonly properties which are never null
don't new() empty learnsets (>800 Learnset objects no longer created,
total of 2400 objects since we also new() a move & level array)
optimize g1/2 reader for early abort case
* Access rewrite
Initialize blocks in a separate object, and get via that object
removes a couple hundred "might be null" warnings since blocks are now readonly getters
some block references have been relocated, but interfaces should expose all that's needed
put HoF6 controls in a groupbox, and disable
* Readonly personal data
* IVs non nullable for mystery gift
* Explicitly initialize forced encounter moves
* Make shadow objects readonly & non-null
Put murkrow fix in binary data resource, instead of on startup
* Assign dex form fetch on constructor
Fixes legality parsing edge cases
also handle cxd parse for valid; exit before exception is thrown in FrameGenerator
* Remove unnecessary null checks
* Keep empty value until init
SetPouch sets the value to an actual one during load, but whatever
* Readonly team lock data
* Readonly locks
Put locked encounters at bottom (favor unlocked)
* Mail readonly data / offset
Rearrange some call flow and pass defaults
Add fake classes for SaveDataEditor mocking
Always party size, no need to check twice in stat editor
use a fake save file as initial data for savedata editor, and for
gamedata (wow i found a usage)
constrain eventwork editor to struct variable types (uint, int, etc),
thus preventing null assignment errors
2019-10-17 01:47:31 +00:00
|
|
|
|
public static bool ExportMGDialog(DataMysteryGift gift, GameVersion origin)
|
2017-05-19 01:10:58 +00:00
|
|
|
|
{
|
2019-10-08 01:40:09 +00:00
|
|
|
|
using var sfd = new SaveFileDialog
|
2017-05-19 01:10:58 +00:00
|
|
|
|
{
|
2020-12-11 04:42:30 +00:00
|
|
|
|
Filter = GetMysterGiftFilter(gift.Generation, origin),
|
2021-08-20 20:49:20 +00:00
|
|
|
|
FileName = Util.CleanFileName(gift.FileName),
|
2017-05-19 01:10:58 +00:00
|
|
|
|
};
|
2019-07-14 22:06:45 +00:00
|
|
|
|
if (sfd.ShowDialog() != DialogResult.OK)
|
2017-05-19 01:10:58 +00:00
|
|
|
|
return false;
|
|
|
|
|
|
2019-07-14 22:06:45 +00:00
|
|
|
|
string path = sfd.FileName;
|
2019-02-17 03:53:14 +00:00
|
|
|
|
SaveBackup(path);
|
2017-05-19 01:10:58 +00:00
|
|
|
|
|
2019-12-25 07:24:28 +00:00
|
|
|
|
File.WriteAllBytes(path, gift.Write());
|
2017-05-19 01:10:58 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-22 02:20:11 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the File Dialog filter for a Mystery Gift I/O operation.
|
|
|
|
|
/// </summary>
|
2019-07-14 22:06:45 +00:00
|
|
|
|
/// <param name="format">Format specifier for the </param>
|
2018-12-30 06:10:33 +00:00
|
|
|
|
/// <param name="origin">Game the format originated from/to</param>
|
2021-01-02 01:08:49 +00:00
|
|
|
|
public static string GetMysterGiftFilter(int format, GameVersion origin) => format switch
|
2017-05-19 01:10:58 +00:00
|
|
|
|
{
|
2021-01-02 01:08:49 +00:00
|
|
|
|
4 => "Gen4 Mystery Gift|*.pgt;*.pcd;*.wc4" + all,
|
|
|
|
|
5 => "Gen5 Mystery Gift|*.pgf" + all,
|
|
|
|
|
6 => "Gen6 Mystery Gift|*.wc6;*.wc6full" + all,
|
|
|
|
|
7 => GameVersion.GG.Contains(origin)
|
|
|
|
|
? "Beluga Gift Record|*.wr7" + all
|
|
|
|
|
: "Gen7 Mystery Gift|*.wc7;*.wc7full" + all,
|
2021-11-20 02:23:49 +00:00
|
|
|
|
8 => GameVersion.BDSP.Contains(origin)
|
|
|
|
|
? "BD/SP Gift|*.wb8" + all
|
|
|
|
|
: "Gen8 Mystery Gift|*.wc8" + all,
|
2021-01-02 01:08:49 +00:00
|
|
|
|
_ => string.Empty,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private const string all = "|All Files|*.*";
|
2016-07-09 22:30:12 +00:00
|
|
|
|
}
|
|
|
|
|
}
|