mirror of
https://github.com/kwsch/pkNX.git
synced 2026-05-03 03:15:21 -05:00
379 lines
12 KiB
C#
379 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Windows;
|
|
using System.Windows.Forms;
|
|
using System.Windows.Input;
|
|
using System.Windows.Interop;
|
|
using FontAwesome.Sharp;
|
|
using PKHeX.Drawing.PokeSprite;
|
|
|
|
using pkNX.Game;
|
|
using pkNX.Structures;
|
|
using pkNX.WinForms.Controls;
|
|
using Button = System.Windows.Controls.Button;
|
|
using Clipboard = System.Windows.Clipboard;
|
|
using DataFormats = System.Windows.DataFormats;
|
|
using DragDropEffects = System.Windows.DragDropEffects;
|
|
|
|
namespace pkNX.WinForms;
|
|
|
|
public readonly struct EditorButtonData
|
|
{
|
|
public static readonly int Width = 140;
|
|
public static readonly int Height = 58;
|
|
public static readonly Thickness Margin = new(3, 3, 3, 3);
|
|
|
|
public static readonly int LayoutWidth = Width + (int)Margin.Left + (int)Margin.Right;
|
|
public static readonly int LayoutHeight = Height + (int)Margin.Top + (int)Margin.Bottom;
|
|
|
|
public string Title { get; init; }
|
|
public IconChar? Icon { get; init; }
|
|
public Action<object, RoutedEventArgs> OnClick { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Interaction logic for MainWindow.xaml
|
|
/// </summary>
|
|
public partial class MainWindow
|
|
{
|
|
public static readonly DependencyProperty CategoriesProperty = DependencyProperty.Register(
|
|
nameof(Categories), typeof(EditorButtonData[]), typeof(MainWindow), new PropertyMetadata(Array.Empty<EditorButtonData>()));
|
|
|
|
private static readonly string[] SupportedLanguages = [
|
|
"カタカナ",
|
|
"漢字",
|
|
"English",
|
|
"Français",
|
|
"Italiano",
|
|
"Deutsch",
|
|
"Español",
|
|
"한국",
|
|
"汉字简化方案",
|
|
"漢字簡化方案",
|
|
];
|
|
|
|
public EditorButtonData[] Categories
|
|
{
|
|
get => (EditorButtonData[])GetValue(CategoriesProperty);
|
|
set => SetValue(CategoriesProperty, value);
|
|
}
|
|
|
|
public ProgramSettings Settings { get; }
|
|
|
|
private new int Language
|
|
{
|
|
get => CB_Lang.SelectedIndex;
|
|
set => CB_Lang.SelectedIndex = value;
|
|
}
|
|
|
|
private EditorBase? Editor;
|
|
|
|
public MainWindow()
|
|
{
|
|
InitializeComponent();
|
|
DataContext = this;
|
|
|
|
CB_Lang.ItemsSource = SupportedLanguages;
|
|
|
|
SpriteName.AllowShinySprite = true;
|
|
SpriteBuilderUtil.SpriterPreference = SpriteBuilderPreference.ForceSprites;
|
|
|
|
Settings = ProgramSettings.LoadSettings();
|
|
CB_Lang.SelectedIndex = Settings.Language;
|
|
Menu_DisplayAdvanced.IsChecked = Settings.DisplayAdvanced;
|
|
|
|
if (!string.IsNullOrWhiteSpace(Settings.GamePath) && Directory.Exists(Settings.GamePath))
|
|
OpenPath(Settings.GamePath, Settings.GameOverride);
|
|
|
|
Drop += (s, e) =>
|
|
{
|
|
var files = (string[]?)e.Data?.GetData(DataFormats.FileDrop);
|
|
if (files is null)
|
|
return;
|
|
foreach (var f in files)
|
|
OpenPath(f);
|
|
};
|
|
DragEnter += (s, e) =>
|
|
{
|
|
if (e.Data?.GetDataPresent(DataFormats.FileDrop) ?? false)
|
|
e.Effects = DragDropEffects.Copy;
|
|
};
|
|
}
|
|
|
|
private void ChangeLanguage(object sender, EventArgs e)
|
|
{
|
|
if (Editor == null)
|
|
return;
|
|
|
|
if (Editor.Game.GetGeneration() < 7 && Language > 7 && !GameVersion.GG.Contains(Editor.Game))
|
|
{
|
|
WinFormsUtil.Alert("Selected Language is not available for this game", "Defaulting to English.");
|
|
CB_Lang.SelectedIndex = 2;
|
|
return;
|
|
}
|
|
Editor.Language = Language;
|
|
}
|
|
|
|
private static string GetClipboardTextString()
|
|
{
|
|
try
|
|
{
|
|
return Clipboard.GetText();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"Unable to read clipboard text: {ex.Message}");
|
|
return string.Empty;
|
|
}
|
|
}
|
|
|
|
private void OpenPath(string path, GameVersion gameOverride = GameVersion.Any)
|
|
{
|
|
try
|
|
{
|
|
if (Directory.Exists(path))
|
|
OpenFolder(path, gameOverride);
|
|
else
|
|
OpenFile(path);
|
|
}
|
|
catch (Exception ex) when (!Debugger.IsAttached)
|
|
{
|
|
WinFormsUtil.Error($"Failed to open -- {path}", ex.Message);
|
|
}
|
|
}
|
|
|
|
private static void OpenFile(string path)
|
|
{
|
|
var result = FileRipper.TryOpenFile(path);
|
|
if (result.Code != RipResultCode.Success || result.ResultPath is not { } resultPath)
|
|
{
|
|
WinFormsUtil.Alert("Invalid file loaded." + Environment.NewLine + $"Unable to recognize data: {result.Code}.", path);
|
|
return;
|
|
}
|
|
|
|
System.Media.SystemSounds.Asterisk.Play();
|
|
Process.Start("explorer.exe", resultPath);
|
|
}
|
|
|
|
private void OpenFolder(string path, GameVersion gameOverride)
|
|
{
|
|
var loadResult = EditorBase.GetEditor(path, Language, gameOverride);
|
|
var (editor, result) = loadResult;
|
|
|
|
while (result == GameLoadResult.RomfsSelected)
|
|
{
|
|
var errorMsg = result.GetErrorMsg();
|
|
|
|
var parentPath = Directory.GetParent(path)?.FullName ?? string.Empty;
|
|
if (string.IsNullOrEmpty(parentPath))
|
|
return;
|
|
|
|
var prompt = $"Clicking 'OK' will allow pkNX to create a working directory in the following folder:\n\"{parentPath}\"";
|
|
|
|
if (WinFormsUtil.Prompt(MessageBoxButton.OKCancel, errorMsg, prompt) != MessageBoxResult.OK)
|
|
return;
|
|
|
|
(editor, result) = EditorBase.GetEditor(parentPath, Language, gameOverride);
|
|
}
|
|
|
|
if (editor == null)
|
|
{
|
|
var msg = "An error occurred while loading the game files." + Environment.NewLine + result.GetErrorMsg();
|
|
WinFormsUtil.Alert(msg, path);
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
editor.Initialize();
|
|
LoadROM(editor);
|
|
}
|
|
catch (Exception ex) when (!Debugger.IsAttached)
|
|
{
|
|
var msg = "Failed to initialize ROM data." + Environment.NewLine +
|
|
"Please ensure your dump is correctly set up, with updated patches merged in (if applicable).";
|
|
var stack = ex.StackTrace ?? string.Empty;
|
|
WinFormsUtil.Error(msg, ex.Message, stack);
|
|
}
|
|
}
|
|
|
|
private void LoadEditorButtons(EditorCategory category = EditorCategory.None)
|
|
{
|
|
List<EditorButtonData> categories = [];
|
|
if (category == EditorCategory.None)
|
|
{
|
|
foreach (var c in (EditorCategory[])Enum.GetValues(typeof(EditorCategory)))
|
|
{
|
|
if (c == EditorCategory.None)
|
|
continue;
|
|
|
|
if (Editor!.CountControlsForCategory(c) == 0)
|
|
continue;
|
|
|
|
categories.Add(new()
|
|
{
|
|
Icon = c.GetIcon(),
|
|
Title = c.ToString(),
|
|
OnClick = (_, _) => LoadEditorButtons(c),
|
|
});
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Create back button
|
|
categories.Add(new()
|
|
{
|
|
Title = "Back",
|
|
Icon = IconChar.Reply,
|
|
OnClick = (_, _) => LoadEditorButtons(),
|
|
});
|
|
}
|
|
|
|
categories.AddRange(Editor!.GetControls(category, Settings.DisplayAdvanced).OrderBy(x => x.Title));
|
|
|
|
Categories = [.. categories];
|
|
|
|
AdjustWindowSize();
|
|
|
|
CenterWindowOnScreen();
|
|
}
|
|
|
|
private void CenterWindowOnScreen()
|
|
{
|
|
var helper = new WindowInteropHelper(this);
|
|
var screen = Screen.FromHandle(helper.Handle);
|
|
var area = screen.WorkingArea;
|
|
|
|
var source = PresentationSource.FromVisual(this);
|
|
var dpi = source?.CompositionTarget?.TransformFromDevice.M11 ?? 1.0;
|
|
|
|
Left = (dpi * area.Left) + (((dpi * area.Width) - Width) / 2);
|
|
Top = (dpi * area.Top) + (((dpi * area.Height) - Height) / 2);
|
|
}
|
|
|
|
private void AdjustWindowSize()
|
|
{
|
|
int wp = EditorButtonData.LayoutWidth;
|
|
int hp = EditorButtonData.LayoutHeight;
|
|
int area = wp * hp;
|
|
var count = Categories.Length;
|
|
// Resize form dimensions then center to screen, so that all buttons are shown.
|
|
var totalArea = count * area;
|
|
var squareSide = Math.Sqrt(totalArea);
|
|
var columns = (int)Math.Ceiling(squareSide / wp) + 1;
|
|
var rows = (count / columns) + 1;
|
|
|
|
double containerHorizontalMargin = SV_Controls.Padding.Left + SV_Controls.Padding.Right;
|
|
double containerVerticalMargin = SV_Controls.Padding.Top + SV_Controls.Padding.Bottom;
|
|
|
|
double titleHeight = (SystemParameters.WindowCaptionHeight + SystemParameters.ResizeFrameHorizontalBorderHeight) * 1.5;
|
|
double verticalBorderWidth = Math.Ceiling((SystemParameters.ResizeFrameVerticalBorderWidth + SystemParameters.FixedFrameVerticalBorderWidth) * 2) + 4;
|
|
|
|
Width = containerHorizontalMargin + (columns * wp) + verticalBorderWidth;
|
|
Height = SP_Header.Height + containerVerticalMargin + (rows * hp) + titleHeight;
|
|
|
|
UpdateLayout();
|
|
|
|
// Adjust for scrollbar width
|
|
//if (SV_Controls.ComputedVerticalScrollBarVisibility == Visibility.Visible)
|
|
// DP_Content.Width += (int)Math.Ceiling(SystemParameters.VerticalScrollBarWidth);
|
|
}
|
|
|
|
private void LoadROM(EditorBase editor)
|
|
{
|
|
Editor?.Close(); // Clean exit prior editor
|
|
Editor = editor;
|
|
|
|
LoadEditorButtons();
|
|
|
|
Title = $"{nameof(pkNX)} - {Editor.Game}";
|
|
L_Path.Content = Editor.Location;
|
|
Menu_Current.IsEnabled = true;
|
|
EditUtil.LoadSettings(Editor.Game);
|
|
EditUtil.SaveSettings(Editor.Game);
|
|
System.Media.SystemSounds.Asterisk.Play();
|
|
}
|
|
|
|
private async void Window_Closing(object sender, CancelEventArgs e)
|
|
{
|
|
if (Editor == null)
|
|
return;
|
|
|
|
Editor.Close();
|
|
EditUtil.SaveSettings(Editor.Game);
|
|
Settings.Language = CB_Lang.SelectedIndex;
|
|
Settings.GamePath = (string)L_Path.Content;
|
|
Settings.GameOverride = Editor.Game;
|
|
await ProgramSettings.SaveSettings(Settings);
|
|
}
|
|
|
|
private void Menu_Current_Click(object sender, EventArgs e)
|
|
{
|
|
if (Directory.Exists((string)L_Path.Content))
|
|
Process.Start("explorer.exe", (string)L_Path.Content);
|
|
}
|
|
|
|
private void Menu_SetRNGSeed_Click(object sender, EventArgs e)
|
|
{
|
|
var result = WinFormsUtil.Prompt(MessageBoxButton.YesNo, "Reseed RNG?",
|
|
"If yes, copy the 32 bit (not hex) integer seed to the clipboard before hitting Yes.");
|
|
if (MessageBoxResult.Yes != result)
|
|
return;
|
|
|
|
string val = GetClipboardTextString();
|
|
if (int.TryParse(val, out int seed))
|
|
{
|
|
Util.Rand = new Random(seed);
|
|
WinFormsUtil.Alert($"Reseeded RNG to seed: {seed}");
|
|
return;
|
|
}
|
|
WinFormsUtil.Alert("Unable to set seed.");
|
|
}
|
|
|
|
private void OpenCommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
|
|
{
|
|
using FolderBrowserDialog fbd = new();
|
|
fbd.Description = "Select a folder containing the game files.";
|
|
fbd.UseDescriptionForTitle = true;
|
|
fbd.ShowNewFolderButton = false;
|
|
|
|
if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
|
|
OpenPath(fbd.SelectedPath);
|
|
}
|
|
|
|
private void SaveCommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
|
|
{
|
|
Editor?.Save();
|
|
}
|
|
|
|
private void ExitCommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
|
|
{
|
|
//if (ModifierKeys == Key.Control) // triggered via hotkey
|
|
{
|
|
if (MessageBoxResult.Yes != WinFormsUtil.Prompt(MessageBoxButton.YesNo, $"Quit {nameof(pkNX)}?"))
|
|
return;
|
|
}
|
|
Close();
|
|
}
|
|
|
|
private void Button_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
EditorButtonData data = (EditorButtonData)(sender as Button)!.DataContext;
|
|
data.OnClick.Invoke(sender, e);
|
|
}
|
|
|
|
private async void Menu_DisplayAdvanced_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
Menu_DisplayAdvanced.IsChecked = !Menu_DisplayAdvanced.IsChecked;
|
|
Settings.DisplayAdvanced = Menu_DisplayAdvanced.IsChecked;
|
|
await ProgramSettings.SaveSettings(Settings);
|
|
|
|
// Force reload of editor buttons
|
|
LoadEditorButtons();
|
|
}
|
|
}
|