mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-05-03 20:17:08 -05:00
BlankSaveFile -> creation of blank save files SaveFileType -> listing of all savefile types Blank save file arg passing is now clearer Instead of SaveFile? return, use TryGet pattern with nullable annotations to indicate success
339 lines
11 KiB
C#
339 lines
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Windows.Forms;
|
|
using PKHeX.Core;
|
|
using static PKHeX.Core.SCBlockUtil;
|
|
|
|
namespace PKHeX.WinForms;
|
|
|
|
public partial class SAV_BlockDump8 : Form
|
|
{
|
|
private readonly ISCBlockArray SAV;
|
|
private readonly SCBlockMetadata Metadata;
|
|
private readonly ComboItem[] SortedBlockKeys;
|
|
|
|
private SCBlock CurrentBlock = null!;
|
|
private string Filter = string.Empty;
|
|
|
|
public SAV_BlockDump8(ISCBlockArray sav)
|
|
{
|
|
InitializeComponent();
|
|
WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage);
|
|
SAV = sav;
|
|
|
|
PG_BlockView.Size = RTB_Hex.Size;
|
|
|
|
// Get an external source of names if available.
|
|
var extra = GetExtraKeyNames(sav);
|
|
Metadata = new SCBlockMetadata(SAV.Accessor, extra, Main.Settings.Advanced.GetExclusionList8());
|
|
|
|
CB_Key.InitializeBinding();
|
|
SortedBlockKeys = Metadata.GetSortedBlockKeyList().ToArray();
|
|
CB_Key.DataSource = SortedBlockKeys;
|
|
|
|
CB_TypeToggle.InitializeBinding();
|
|
CB_TypeToggle.DataSource = new[]
|
|
{
|
|
new ComboItem(nameof(SCTypeCode.Bool1), (int)SCTypeCode.Bool1),
|
|
new ComboItem(nameof(SCTypeCode.Bool2), (int)SCTypeCode.Bool2),
|
|
};
|
|
CB_TypeToggle.SelectedIndexChanged += CB_TypeToggle_SelectedIndexChanged;
|
|
CB_Key.KeyDown += WinFormsUtil.RemoveDropCB;
|
|
|
|
CB_Key.SelectedIndex = 0;
|
|
}
|
|
|
|
private static IEnumerable<string> GetExtraKeyNames(ISCBlockArray obj)
|
|
{
|
|
var extra = Main.Settings.Advanced.PathBlockKeyList;
|
|
if (extra.Length != 0 && !Directory.Exists(extra))
|
|
return [];
|
|
|
|
var file = Path.Combine(extra, obj.GetType().Name);
|
|
file = $"{file}.txt";
|
|
if (!File.Exists(file))
|
|
return [];
|
|
|
|
return File.ReadLines(file);
|
|
}
|
|
|
|
private void CB_Key_SelectedIndexChanged(object sender, EventArgs e)
|
|
{
|
|
var key = (uint)WinFormsUtil.GetIndex(CB_Key);
|
|
CurrentBlock = SAV.Accessor.GetBlock(key);
|
|
UpdateBlockSummaryControls();
|
|
if (CurrentBlock.Type.IsBoolean())
|
|
{
|
|
CB_TypeToggle.SelectedValue = (int)CurrentBlock.Type;
|
|
CB_TypeToggle.Visible = true;
|
|
}
|
|
else
|
|
{
|
|
CB_TypeToggle.Visible = false;
|
|
}
|
|
}
|
|
|
|
private void UpdateBlockSummaryControls()
|
|
{
|
|
var block = CurrentBlock;
|
|
L_Detail_R.Text = GetBlockSummary(block);
|
|
|
|
var sb = new StringBuilder();
|
|
foreach (var b in block.Data)
|
|
sb.Append($"{b:X2} ");
|
|
RTB_Hex.Text = sb.ToString();
|
|
|
|
var blockName = Metadata.GetBlockName(block, out var obj);
|
|
if (blockName is not null)
|
|
{
|
|
L_BlockName.Visible = true;
|
|
L_BlockName.Text = blockName;
|
|
}
|
|
else
|
|
{
|
|
L_BlockName.Visible = false;
|
|
}
|
|
|
|
if (ModifierKeys != Keys.Control)
|
|
{
|
|
// Show a PropertyGrid to edit
|
|
if (obj is not null)
|
|
{
|
|
var props = ReflectUtil.GetPropertiesCanWritePublicDeclared(obj.GetType());
|
|
if (props.Count() > 1 || ModifierKeys == Keys.Shift)
|
|
{
|
|
PG_BlockView.Visible = true;
|
|
PG_BlockView.SelectedObject = obj;
|
|
return;
|
|
}
|
|
}
|
|
|
|
var o = SCBlockMetadata.GetEditableBlockObject(block);
|
|
if (o is not null)
|
|
{
|
|
PG_BlockView.Visible = true;
|
|
PG_BlockView.SelectedObject = o;
|
|
return;
|
|
}
|
|
}
|
|
PG_BlockView.Visible = false;
|
|
}
|
|
|
|
private void CB_TypeToggle_SelectedIndexChanged(object? sender, EventArgs e)
|
|
{
|
|
var block = CurrentBlock;
|
|
var cType = block.Type;
|
|
var cValue = (SCTypeCode)WinFormsUtil.GetIndex(CB_TypeToggle);
|
|
if (cType == cValue)
|
|
return;
|
|
block.ChangeBooleanType(cValue);
|
|
UpdateBlockSummaryControls();
|
|
}
|
|
|
|
private void B_ExportAll_Click(object sender, EventArgs e)
|
|
{
|
|
using var fbd = new FolderBrowserDialog();
|
|
if (fbd.ShowDialog() != DialogResult.OK)
|
|
return;
|
|
var path = fbd.SelectedPath;
|
|
var blocks = SAV.AllBlocks;
|
|
ExportAllBlocks(blocks, path);
|
|
}
|
|
|
|
private static void ExportAllBlocks(IEnumerable<SCBlock> blocks, string path)
|
|
{
|
|
foreach (var b in blocks.Where(z => z.Data.Length != 0))
|
|
{
|
|
var fn = $"{GetBlockFileNameWithoutExtension(b)}.bin";
|
|
var fileName = Path.Combine(path, fn);
|
|
File.WriteAllBytes(fileName, b.Data);
|
|
}
|
|
}
|
|
|
|
private void B_ImportFolder_Click(object sender, EventArgs e)
|
|
{
|
|
using var fbd = new FolderBrowserDialog();
|
|
if (fbd.ShowDialog() != DialogResult.OK)
|
|
return;
|
|
|
|
var failed = ImportBlocksFromFolder(fbd.SelectedPath, SAV);
|
|
if (failed.Count != 0)
|
|
{
|
|
var msg = string.Join(Environment.NewLine, failed);
|
|
WinFormsUtil.Error("Failed to import:", msg);
|
|
}
|
|
}
|
|
|
|
private void B_ImportCurrent_Click(object sender, EventArgs e) => ImportSelectBlock(CurrentBlock);
|
|
private void B_ExportCurrent_Click(object sender, EventArgs e) => ExportSelectBlock(CurrentBlock);
|
|
|
|
private void B_ExportAllSingle_Click(object sender, EventArgs e)
|
|
{
|
|
using var sfd = new SaveFileDialog();
|
|
sfd.FileName = "raw.bin";
|
|
if (sfd.ShowDialog() != DialogResult.OK)
|
|
return;
|
|
|
|
var path = sfd.FileName;
|
|
var blocks = SAV.Accessor.BlockInfo;
|
|
var option = GetExportOption();
|
|
|
|
ExportAllBlocksAsSingleFile(blocks, path, option);
|
|
}
|
|
|
|
private SCBlockExportOption GetExportOption()
|
|
{
|
|
var option = SCBlockExportOption.None;
|
|
if (CHK_DataOnly.Checked)
|
|
option |= SCBlockExportOption.DataOnly;
|
|
if (CHK_Key.Checked)
|
|
option |= SCBlockExportOption.Key;
|
|
if (CHK_Type.Checked)
|
|
option |= SCBlockExportOption.TypeInfo;
|
|
if (CHK_FakeHeader.Checked)
|
|
option |= SCBlockExportOption.FakeHeader;
|
|
return option;
|
|
}
|
|
|
|
private void B_LoadOld_Click(object sender, EventArgs e)
|
|
{
|
|
using var ofd = new OpenFileDialog();
|
|
ofd.Title = MessageStrings.MsgFileLoadSaveSelectGame;
|
|
ofd.FileName = "main";
|
|
if (ofd.ShowDialog() != DialogResult.OK)
|
|
return;
|
|
TB_OldSAV.Text = ofd.FileName;
|
|
if (!string.IsNullOrWhiteSpace(TB_NewSAV.Text))
|
|
CompareSaves();
|
|
}
|
|
|
|
private void B_LoadNew_Click(object sender, EventArgs e)
|
|
{
|
|
using var ofd = new OpenFileDialog();
|
|
ofd.FileName = "main";
|
|
if (ofd.ShowDialog() != DialogResult.OK)
|
|
return;
|
|
TB_NewSAV.Text = ofd.FileName;
|
|
if (!string.IsNullOrWhiteSpace(TB_OldSAV.Text))
|
|
CompareSaves();
|
|
}
|
|
|
|
private void CompareSaves()
|
|
{
|
|
var p1 = TB_OldSAV.Text;
|
|
var p2 = TB_NewSAV.Text;
|
|
|
|
var f1 = new FileInfo(p1);
|
|
if (!SaveUtil.IsSizeValid((int)f1.Length))
|
|
return;
|
|
var f2 = new FileInfo(p1);
|
|
if (!SaveUtil.IsSizeValid((int)f2.Length))
|
|
return;
|
|
|
|
if (!SaveUtil.TryGetSaveFile(p1, out var s1) || s1 is not ISCBlockArray w1)
|
|
return;
|
|
if (!SaveUtil.TryGetSaveFile(p2, out var s2) || s2 is not ISCBlockArray w2)
|
|
return;
|
|
|
|
// Get an external source of names if available.
|
|
var extra = GetExtraKeyNames(w1);
|
|
var compare = new SCBlockCompare(w1.Accessor, w2.Accessor, extra);
|
|
richTextBox1.Lines = [.. compare.Summary()];
|
|
}
|
|
|
|
private static void ExportSelectBlock(SCBlock block)
|
|
{
|
|
var name = GetBlockFileNameWithoutExtension(block);
|
|
using var sfd = new SaveFileDialog();
|
|
sfd.FileName = $"{name}.bin";
|
|
if (sfd.ShowDialog() != DialogResult.OK)
|
|
return;
|
|
File.WriteAllBytes(sfd.FileName, block.Data);
|
|
}
|
|
|
|
private static void ImportSelectBlock(SCBlock blockTarget)
|
|
{
|
|
var key = blockTarget.Key;
|
|
var data = blockTarget.Data;
|
|
using var ofd = new OpenFileDialog();
|
|
ofd.Title = MessageStrings.MsgFileLoadSelectFileBlock;
|
|
ofd.FileName = $"{key:X8}.bin";
|
|
if (ofd.ShowDialog() != DialogResult.OK)
|
|
return;
|
|
|
|
var path = ofd.FileName;
|
|
var file = new FileInfo(path);
|
|
if (file.Length != data.Length)
|
|
{
|
|
WinFormsUtil.Error(string.Format(MessageStrings.MsgFileSize, $"0x{file.Length:X8}"));
|
|
return;
|
|
}
|
|
|
|
var bytes = File.ReadAllBytes(path);
|
|
blockTarget.ChangeData(bytes);
|
|
}
|
|
|
|
private void PG_BlockView_PropertyValueChanged(object s, PropertyValueChangedEventArgs? e)
|
|
{
|
|
if (e?.ChangedItem is not null)
|
|
Debug.WriteLine($"ChangedItem = {e.ChangedItem.Label}, OldValue = {e.OldValue}, NewValue = {e.ChangedItem.Value}");
|
|
if (CurrentBlock.Type != SCTypeCode.Object && CurrentBlock.Type != SCTypeCode.Array)
|
|
L_Detail_R.Text = GetBlockSummary(CurrentBlock);
|
|
}
|
|
|
|
private void CB_Key_KeyDown(object sender, KeyEventArgs e)
|
|
{
|
|
if (e.KeyCode != Keys.Enter)
|
|
return;
|
|
|
|
var text = CB_Key.Text.Trim();
|
|
if (text.Length == 8)
|
|
{
|
|
var hex = (int)Util.GetHexValue(text);
|
|
if (hex != 0)
|
|
{
|
|
if (!string.IsNullOrEmpty(Filter))
|
|
{
|
|
// Clear the filter
|
|
CB_Key.DataSource = SortedBlockKeys;
|
|
Filter = string.Empty;
|
|
}
|
|
// Input is hexadecimal number, select the item -- if it exists.
|
|
bool exists = SortedBlockKeys.Any(z => z.Value == hex);
|
|
if (exists)
|
|
{
|
|
CB_Key.SelectedValue = hex;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (CB_Key.SelectedItem is not null && text.Equals(CB_Key.SelectedText))
|
|
return; // User press enter on selected item
|
|
|
|
if (Filter.Equals(text, StringComparison.InvariantCultureIgnoreCase))
|
|
return; // Filter hasn't changed
|
|
|
|
Filter = text;
|
|
if (string.IsNullOrEmpty(text))
|
|
{
|
|
// User has cleared the filter. Restore original metadata
|
|
CB_Key.DataSource = SortedBlockKeys;
|
|
CB_Key.SelectedIndex = 0;
|
|
return;
|
|
}
|
|
|
|
// Filter combo items that contains input text
|
|
var filtered = Array.FindAll(SortedBlockKeys, x => x.Text.Contains(text, StringComparison.InvariantCultureIgnoreCase));
|
|
if (filtered.Length == 0)
|
|
return; // no results
|
|
|
|
CB_Key.DataSource = filtered;
|
|
CB_Key.SelectedIndex = 0;
|
|
}
|
|
}
|