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;
|
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;
|
|
|
|
|
|
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
|
|
|
|
|
2017-05-13 17:20:25 +00:00
|
|
|
|
internal static void CenterToForm(this Control child, Control parent)
|
2016-07-09 22:30:12 +00:00
|
|
|
|
{
|
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));
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-13 01:11:47 +00:00
|
|
|
|
public static Form FirstFormOfType<T>(this Form f) => Array.Find(f.OwnedForms, form => form is T);
|
2018-07-22 02:20:11 +00:00
|
|
|
|
|
2018-05-05 15:07:22 +00:00
|
|
|
|
public static T FindFirstControlOfType<T>(Control aParent) where T : class
|
|
|
|
|
{
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
var t = aParent as T;
|
|
|
|
|
if (t != null)
|
|
|
|
|
return t;
|
|
|
|
|
|
|
|
|
|
if (aParent.Parent != null)
|
|
|
|
|
aParent = aParent.Parent;
|
|
|
|
|
else
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-07-22 02:20:11 +00:00
|
|
|
|
|
2017-05-23 04:55:05 +00:00
|
|
|
|
public static Control GetUnderlyingControl(object sender) => ((sender as ToolStripItem)?.Owner as ContextMenuStrip)?.SourceControl ?? sender as PictureBox;
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
System.Media.SystemSounds.Exclamation.Play();
|
|
|
|
|
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
|
|
|
|
|
2016-07-09 22:30:12 +00:00
|
|
|
|
internal static DialogResult Alert(params string[] lines)
|
|
|
|
|
{
|
|
|
|
|
System.Media.SystemSounds.Asterisk.Play();
|
|
|
|
|
string msg = string.Join(Environment.NewLine + Environment.NewLine, lines);
|
2016-09-21 02:25:53 +00:00
|
|
|
|
return MessageBox.Show(msg, "Alert", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
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)
|
|
|
|
|
{
|
|
|
|
|
System.Media.SystemSounds.Question.Play();
|
|
|
|
|
string msg = string.Join(Environment.NewLine + Environment.NewLine, lines);
|
|
|
|
|
return MessageBox.Show(msg, "Prompt", btn, MessageBoxIcon.Asterisk);
|
|
|
|
|
}
|
2018-07-14 23:00:28 +00:00
|
|
|
|
#endregion
|
2016-07-09 22:30:12 +00:00
|
|
|
|
|
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>
|
2017-06-18 01:37:19 +00:00
|
|
|
|
internal static int GetIndex(ComboBox cb)
|
2016-07-09 22:30:12 +00:00
|
|
|
|
{
|
|
|
|
|
return (int)(cb?.SelectedValue ?? 0);
|
|
|
|
|
}
|
2016-07-29 23:00:56 +00:00
|
|
|
|
|
2016-07-31 03:57:45 +00:00
|
|
|
|
public static void PanelScroll(object sender, ScrollEventArgs e)
|
2016-07-29 23:00:56 +00:00
|
|
|
|
{
|
2018-02-22 04:35:07 +00:00
|
|
|
|
if (!(sender is Panel 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:
|
|
|
|
|
p.HorizontalScroll.Value = e.NewValue;
|
|
|
|
|
break;
|
|
|
|
|
case ScrollOrientation.VerticalScroll:
|
|
|
|
|
p.VerticalScroll.Value = e.NewValue;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
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();
|
|
|
|
|
PropertyInfo pi = dgvType.GetProperty("DoubleBuffered", BindingFlags.Instance | BindingFlags.NonPublic);
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public static void RemoveDropCB(object sender, KeyEventArgs e) => ((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>
|
|
|
|
|
/// <param name="control"></param>
|
|
|
|
|
/// <param name="type"></param>
|
|
|
|
|
/// <returns></returns>
|
2017-06-18 04:49:14 +00:00
|
|
|
|
public static IEnumerable<Control> GetAllControlsOfType(Control control, Type type)
|
|
|
|
|
{
|
|
|
|
|
var controls = control.Controls.Cast<Control>().ToList();
|
|
|
|
|
return controls.SelectMany(ctrl => GetAllControlsOfType(ctrl, type))
|
|
|
|
|
.Concat(controls)
|
|
|
|
|
.Where(c => c.GetType() == type);
|
|
|
|
|
}
|
2017-01-08 07:54:09 +00:00
|
|
|
|
|
|
|
|
|
#if CLICKONCE
|
2018-02-22 04:35:07 +00:00
|
|
|
|
public static bool IsClickonceDeployed => System.Deployment.Application.ApplicationDeployment.IsNetworkDeployed;
|
2017-01-08 07:54:09 +00:00
|
|
|
|
#else
|
2018-02-22 04:35:07 +00:00
|
|
|
|
public static bool IsClickonceDeployed => false;
|
2017-01-08 07:54:09 +00:00
|
|
|
|
#endif
|
2017-05-18 05:00:06 +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>
|
2018-09-03 17:30:35 +00:00
|
|
|
|
public static void AddSaveFileExtensions(IEnumerable<string> exts) => CustomSaveExtensions.AddRange(exts.Except(CustomSaveExtensions));
|
2018-07-22 02:20:11 +00:00
|
|
|
|
|
2018-05-22 02:12:02 +00:00
|
|
|
|
private static readonly List<string> CustomSaveExtensions = new List<string>
|
|
|
|
|
{
|
|
|
|
|
// 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
|
|
|
|
|
2018-05-22 02:12:02 +00:00
|
|
|
|
private static string ExtraSaveExtensions => ";" + string.Join(";", CustomSaveExtensions.Select(z => $"*.{z}"));
|
|
|
|
|
|
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>
|
|
|
|
|
/// <param name="Extensions">Misc extensions of <see cref="PKM"/> files supported by the SAV.</param>
|
|
|
|
|
/// <param name="path">Output result path</param>
|
|
|
|
|
/// <returns>Result of whether or not a file is to be loaded from the output path.</returns>
|
2018-02-22 04:35:07 +00:00
|
|
|
|
public static bool OpenSAVPKMDialog(IEnumerable<string> Extensions, out string path)
|
2017-05-18 05:00:06 +00:00
|
|
|
|
{
|
2017-09-30 05:58:25 +00:00
|
|
|
|
string supported = string.Join(";", Extensions.Select(s => $"*.{s}").Concat(new[] { "*.pkm" }));
|
2017-05-18 05:00:06 +00:00
|
|
|
|
OpenFileDialog ofd = new OpenFileDialog
|
|
|
|
|
{
|
|
|
|
|
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" +
|
|
|
|
|
"|Backup File|*.bak"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Detect main
|
2018-07-15 20:35:58 +00:00
|
|
|
|
string msg = null;
|
2018-09-03 17:40:40 +00:00
|
|
|
|
var sav = SaveDetection.DetectSaveFile(Environment.GetLogicalDrives(), ref msg);
|
2018-07-15 20:35:58 +00:00
|
|
|
|
if (sav == null && !string.IsNullOrWhiteSpace(msg))
|
|
|
|
|
Error(msg);
|
|
|
|
|
|
|
|
|
|
if (sav != null)
|
|
|
|
|
ofd.FileName = sav.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;
|
2018-02-22 04:35:07 +00:00
|
|
|
|
bool allowEncrypted = pk.Format > 3 || pk is PK3;
|
2017-05-18 05:00:06 +00:00
|
|
|
|
SaveFileDialog sfd = new SaveFileDialog
|
|
|
|
|
{
|
|
|
|
|
Filter = $"Decrypted PKM File|*.{pkx}" +
|
2018-02-23 06:42:50 +00:00
|
|
|
|
(allowEncrypted ? $"|Encrypted PKM File|*.e{pkx.Substring(1)}" : "") +
|
2017-05-18 05:00:06 +00:00
|
|
|
|
"|Binary File|*.bin" +
|
|
|
|
|
"|All Files|*.*",
|
|
|
|
|
DefaultExt = pkx,
|
|
|
|
|
FileName = Util.CleanFileName(pk.FileName)
|
|
|
|
|
};
|
|
|
|
|
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);
|
|
|
|
|
var data = $".{pkx}" == ext ? pk.DecryptedBoxData : pk.EncryptedPartyData;
|
|
|
|
|
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>
|
|
|
|
|
/// <param name="SAV"><see cref="SaveFile"/> to be saved.</param>
|
|
|
|
|
/// <param name="CurrentBox">Box the player will be greeted with when accessing the PC ingame.</param>
|
|
|
|
|
/// <returns>Result of whether or not the file was saved.</returns>
|
|
|
|
|
public static bool SaveSAVDialog(SaveFile SAV, int CurrentBox = 0)
|
|
|
|
|
{
|
|
|
|
|
// Chunk Error Checking
|
|
|
|
|
string err = SAV.MiscSaveChecks();
|
2018-04-07 04:23:09 +00:00
|
|
|
|
if (err.Length > 0 && Prompt(MessageBoxButtons.YesNo, err, MsgSaveExportContinue) != DialogResult.Yes)
|
2017-05-18 05:00:06 +00:00
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
SaveFileDialog main = new SaveFileDialog
|
|
|
|
|
{
|
|
|
|
|
Filter = SAV.Filter,
|
|
|
|
|
FileName = SAV.FileName,
|
2018-05-22 02:12:02 +00:00
|
|
|
|
FilterIndex = 1000, // default to last, All Files
|
2017-05-18 05:00:06 +00:00
|
|
|
|
RestoreDirectory = true
|
|
|
|
|
};
|
2018-07-17 04:00:43 +00:00
|
|
|
|
if (Directory.Exists(SAV.FileFolder))
|
|
|
|
|
main.InitialDirectory = SAV.FileFolder;
|
2017-05-18 05:00:06 +00:00
|
|
|
|
|
|
|
|
|
// Export
|
|
|
|
|
if (main.ShowDialog() != DialogResult.OK)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (SAV.HasBox)
|
|
|
|
|
SAV.CurrentBox = CurrentBox;
|
|
|
|
|
|
2018-04-07 04:23:09 +00:00
|
|
|
|
var ext = Path.GetExtension(main.FileName)?.ToLower();
|
|
|
|
|
bool dsv = ext == ".dsv";
|
|
|
|
|
bool gci = ext == ".gci";
|
2017-05-18 05:00:06 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
File.WriteAllBytes(main.FileName, SAV.Write(dsv, gci));
|
|
|
|
|
SAV.Edited = false;
|
2018-04-07 04:23:09 +00:00
|
|
|
|
Alert(MsgSaveExportSuccessPath, main.FileName);
|
2017-05-18 05:00:06 +00:00
|
|
|
|
}
|
|
|
|
|
catch (Exception x)
|
|
|
|
|
{
|
|
|
|
|
if (x is UnauthorizedAccessException || x is FileNotFoundException || x is IOException)
|
2018-04-07 04:23:09 +00:00
|
|
|
|
Error(MsgFileWriteFail + Environment.NewLine + x.Message, MsgFileWriteProtectedAdvice);
|
2017-05-18 05:00:06 +00:00
|
|
|
|
else throw;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
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>
|
|
|
|
|
/// <returns>Result of whether or not the file was saved.</returns>
|
|
|
|
|
public static bool SaveMGDialog(MysteryGift gift)
|
|
|
|
|
{
|
|
|
|
|
SaveFileDialog output = new SaveFileDialog
|
|
|
|
|
{
|
2017-06-18 01:37:19 +00:00
|
|
|
|
Filter = GetMysterGiftFilter(gift.Format),
|
2017-05-19 01:10:58 +00:00
|
|
|
|
FileName = Util.CleanFileName(gift.FileName)
|
|
|
|
|
};
|
|
|
|
|
if (output.ShowDialog() != DialogResult.OK)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
string path = output.FileName;
|
|
|
|
|
|
|
|
|
|
if (File.Exists(path))
|
|
|
|
|
{
|
|
|
|
|
// File already exists, save a .bak
|
2017-10-02 04:25:23 +00:00
|
|
|
|
string bakpath = $"{path}.bak";
|
2017-05-19 01:10:58 +00:00
|
|
|
|
if (!File.Exists(bakpath))
|
2017-10-02 04:25:23 +00:00
|
|
|
|
File.Move(path, bakpath);
|
2017-05-19 01:10:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
File.WriteAllBytes(path, gift.Data);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-22 02:20:11 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the File Dialog filter for a Mystery Gift I/O operation.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="Format">Format specifier for the </param>
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public static string GetMysterGiftFilter(int Format)
|
2017-05-19 01:10:58 +00:00
|
|
|
|
{
|
2018-07-22 02:20:11 +00:00
|
|
|
|
const string all = "|All Files|*.*";
|
2017-05-19 01:10:58 +00:00
|
|
|
|
switch (Format)
|
|
|
|
|
{
|
2018-07-22 02:20:11 +00:00
|
|
|
|
case 4: return "Gen4 Mystery Gift|*.pgt;*.pcd;*.wc4" + all;
|
|
|
|
|
case 5: return "Gen5 Mystery Gift|*.pgf" + all;
|
|
|
|
|
case 6: return "Gen6 Mystery Gift|*.wc6;*.wc6full" + all;
|
|
|
|
|
case 7: return "Gen7 Mystery Gift|*.wc7;*.wc7full" + all;
|
2018-02-22 04:35:07 +00:00
|
|
|
|
default: return string.Empty;
|
2017-05-19 01:10:58 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-07-09 22:30:12 +00:00
|
|
|
|
}
|
|
|
|
|
}
|