pk3DS/pk3DS.WinForms/Tools/ToolsUI.cs
2024-06-02 18:30:56 -05:00

393 lines
15 KiB
C#

using pk3DS.Core.CTR;
using System;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
using pk3DS.Core;
namespace pk3DS.WinForms;
public sealed partial class ToolsUI : Form
{
public ToolsUI()
{
InitializeComponent();
AllowDrop = PB_Unpack.AllowDrop = PB_Repack.AllowDrop = PB_BCLIM.AllowDrop = true;
DragEnter += TabMain_DragEnter;
DragDrop += TabMain_DragDrop;
PB_Unpack.DragEnter += TabMain_DragEnter;
PB_Unpack.DragDrop += TabMain_DragDrop;
PB_Repack.DragEnter += TabMain_DragEnter;
PB_Repack.DragDrop += TabMain_DragDrop;
PB_BCLIM.DragEnter += TabMain_DragEnter;
PB_BCLIM.DragDrop += TabMain_DragDrop;
CLIMWindow = PB_BCLIM.Size;
CB_Repack.Items.Add("Autodetect");
CB_Repack.Items.Add("GARC Pack");
CB_Repack.Items.Add("DARC Pack (use filenames)");
CB_Repack.Items.Add("Mini Pack (from Name)");
CB_Repack.SelectedIndex = 0;
}
private void TabMain_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
e.Effect = DragDropEffects.Copy;
}
private void TabMain_DragDrop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
foreach (var path in files)
HandleDrop(sender, path);
System.Media.SystemSounds.Asterisk.Play();
}
private void HandleDrop(object sender, string path)
{
if (sender == PB_Unpack)
OpenARC(path, pBar1);
else if (sender == PB_BCLIM)
OpenIMG(path);
else if (sender == PB_Repack)
SaveARC(path);
else
DecompressLZSS_BLZ(path);
}
private void DecompressLZSS_BLZ(string path)
{
try
{
LZSS.Decompress(path, Path.Combine(Path.GetDirectoryName(path), "dec_" + Path.GetFileName(path)));
File.Delete(path);
}
catch
{
try
{
if (threads < 1)
new Thread(() => { Interlocked.Increment(ref threads); new BLZCoder(["-d", path], pBar1); Interlocked.Decrement(ref threads); WinFormsUtil.Alert("Decompressed!"); }).Start();
}
catch { WinFormsUtil.Error("Unable to process file."); threads = 0; }
}
}
private void DropHover(object sender, EventArgs e) => ((Panel)sender).BackColor = Color.Gray;
private void DropLeave(object sender, EventArgs e) => ((Panel)sender).BackColor = Color.Transparent;
private void OpenIMG(string path)
{
var img = BCLIM.MakeBMP(path, CHK_PNG.Checked);
if (img == null)
{
try
{
var flim = new BFLIM(path);
if (!flim.Footer.Valid)
return;
img = flim.GetBitmap();
}
catch (Exception ree)
{
Console.WriteLine(ree.Message);
return;
}
if (CHK_PNG.Checked)
{
var dir = Path.GetDirectoryName(path);
var fn = Path.GetFileNameWithoutExtension(path);
var outpath = Path.Combine(dir, $"{fn}.png");
img.Save(outpath);
}
}
PB_BCLIM.Size = new Size(img.Width + 2, img.Height + 2);
PB_BCLIM.BackgroundImage = img;
int leftpad = PB_BCLIM.Location.X;
int suggestedWidth = (leftpad * 2) + PB_BCLIM.Width + 10;
if (Width < suggestedWidth)
Width = suggestedWidth;
int suggestedHeight = PB_BCLIM.Location.Y + PB_BCLIM.Height + leftpad + 30;
if (Height < suggestedHeight)
Height = suggestedHeight;
}
internal static volatile int threads;
internal static void OpenARC(string path, ProgressBar pBar1, bool recursing = false)
{
string newFolder = "";
try
{
// Pre-check file length to see if it is at least valid.
var fi = new FileInfo(path);
if (fi.Length > (long)2 * (1 << 30))
{ WinFormsUtil.Error("File is too big!"); return; } // 2 GB
string folderPath = Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path));
byte[] first4 = new byte[4];
try
{
using var fs = new FileStream(path, FileMode.Open);
using var bw = new BinaryReader(fs);
first4 = bw.ReadBytes(4);
}
catch (Exception e)
{
WinFormsUtil.Error("Cannot open file!", e.ToString());
}
// Determine if it is a DARC or a Mini
// Check if Mini first
string fx = fi.Length > 10 * (1 << 20) ? null : Mini.GetIsMini(path); // no mini is above 10MB
if (fx != null) // Is Mini Packed File
{
newFolder = folderPath + "_" + fx;
// Fetch Mini File Contents
Mini.UnpackMini(path, fx, newFolder, false);
// Recurse throught the extracted contents if they extract successfully
if (Directory.Exists(newFolder))
{
foreach (string file in Directory.GetFiles(newFolder))
OpenARC(file, pBar1, true);
BatchRenameExtension(newFolder);
}
}
else if (first4.SequenceEqual(BitConverter.GetBytes(0x54594C41))) // ALYT
{
if (threads > 0) { WinFormsUtil.Alert("Please wait for all operations to finish first."); return; }
new Thread(() =>
{
Interlocked.Increment(ref threads);
var alyt = new ALYT(File.ReadAllBytes(path));
var sarc = new SARC(alyt.Data) // rip out sarc
{
FileName = Path.GetFileNameWithoutExtension(path) + "_sarc",
FilePath = Path.GetDirectoryName(path),
};
if (!sarc.Valid)
return;
var files = sarc.Dump();
foreach (var _ in files)
{
// openARC(file, pBar1, true);
}
Interlocked.Decrement(ref threads);
}).Start();
}
else if (first4.SequenceEqual(BitConverter.GetBytes(0x47415243))) // GARC
{
if (threads > 0) { WinFormsUtil.Alert("Please wait for all operations to finish first."); return; }
bool SkipDecompression = ModifierKeys == Keys.Control;
new Thread(() =>
{
Interlocked.Increment(ref threads);
bool r = GarcUtil.UnpackGARC(path, folderPath + "_g", SkipDecompression, pBar1);
Interlocked.Decrement(ref threads);
if (r)
{
BatchRenameExtension(newFolder);
}
else
{ WinFormsUtil.Alert("Unpacking failed."); return; }
System.Media.SystemSounds.Asterisk.Play();
}).Start();
}
else if (ARC.Analyze(path).valid) // DARC
{
var data = File.ReadAllBytes(path);
int pos = 0;
while (BitConverter.ToUInt32(data, pos) != 0x63726164)
{
pos += 4;
if (pos >= data.Length) return;
}
var darcData = data.Skip(pos).ToArray();
newFolder = folderPath + "_d";
bool r = Core.CTR.DARC.Darc2files(darcData, newFolder);
if (!r)
{ WinFormsUtil.Alert("Unpacking failed."); }
}
else if (ARC.AnalyzeSARC(path).Valid)
{
var sarc = ARC.AnalyzeSARC(path);
Console.WriteLine($"New SARC with {sarc.SFAT.EntryCount} files.");
foreach (var _ in sarc.Dump(path))
{
}
}
else if (!recursing)
{ WinFormsUtil.Alert("File is not a darc or a mini packed file:" + Environment.NewLine + path); }
}
catch (Exception e)
{
if (!recursing)
WinFormsUtil.Error("File error:" + Environment.NewLine + path, e.ToString());
threads = 0;
}
}
private void SaveARC(string path)
{
if (!Directory.Exists(path)) { WinFormsUtil.Error("Input path is not a Folder", path); return; }
string folderName = Path.GetFileName(path);
string parentName = Directory.GetParent(path).FullName;
int type = CB_Repack.SelectedIndex;
switch (type)
{
case 0: // AutoDetect
{
if (!folderName.Contains('_'))
{ WinFormsUtil.Alert("Unable to autodetect pack type."); return; }
if (folderName.Contains("_g"))
goto case 1;
if (folderName.Contains("_d"))
goto case 2;
// else
goto case 3;
}
case 1: // GARC Pack
{
if (threads > 0) { WinFormsUtil.Alert("Please wait for all operations to finish first."); return; }
DialogResult dr = WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, "Format Selection:",
"Yes: Sun/Moon (Version 6)\nNo: XY/ORAS (Version 4)");
if (dr == DialogResult.Cancel)
return;
var version = dr == DialogResult.Yes ? GARC.VER_6 : GARC.VER_4;
int padding = (int)NUD_Padding.Value;
if (version == GARC.VER_4)
padding = 4;
string outfolder = Directory.GetParent(path).FullName;
new Thread(() =>
{
bool r = GarcUtil.PackGARC(path, Path.Combine(outfolder, folderName + ".garc"), version, padding, pBar1);
if (!r) { WinFormsUtil.Alert("Packing failed."); return; }
// Delete path after repacking
if (CHK_Delete.Checked && Directory.Exists(path))
Directory.Delete(path, true);
System.Media.SystemSounds.Asterisk.Play();
}).Start();
return;
}
case 2: // DARC Pack (from existing if exists)
{
string oldFile = path.Replace("_d", "");
if (File.Exists(Path.Combine(parentName, oldFile)))
oldFile = Path.Combine(parentName, oldFile);
else if (File.Exists(Path.Combine(parentName, oldFile + ".bin")))
oldFile = Path.Combine(parentName, oldFile + ".bin");
else if (File.Exists(Path.Combine(parentName, oldFile + ".darc")))
oldFile = Path.Combine(parentName, oldFile + ".darc");
else oldFile = null;
bool r = Core.CTR.DARC.Files2darc(path, false, oldFile);
if (!r) WinFormsUtil.Alert("Packing failed.");
break;
}
case 3: // Mini Pack
{
// Get Folder Name
string fileName = Path.GetFileName(path);
if (fileName.Length < 3) { WinFormsUtil.Error("Mini Folder name not valid:", path); return; }
int index = fileName.LastIndexOf('_');
string fileNum = fileName[..index];
string fileExt = fileName[(index + 1)..];
// Find old file for reference...
string file;
if (File.Exists(Path.Combine(parentName, fileNum + ".bin")))
file = Path.Combine(parentName, fileNum + ".bin");
else if (File.Exists(Path.Combine(parentName, fileNum + "." + fileExt)))
file = Path.Combine(parentName, fileNum + "." + fileExt);
else
file = null;
byte[] oldData = file != null ? File.ReadAllBytes(file) : null;
bool r = Mini.PackMini2(path, fileExt, Path.Combine(parentName, fileNum + "." + fileExt));
if (!r)
{
WinFormsUtil.Alert("Packing failed.");
break;
}
// Check to see if the header size is different...
if (oldData == null) // No data to compare to.
break;
byte[] newData = File.ReadAllBytes(Path.Combine(parentName, fileNum + "." + fileExt));
if (newData[2] == oldData[2])
{
int newPtr = BitConverter.ToInt32(newData, 4);
int oldPtr = BitConverter.ToInt32(oldData, 4);
if (newPtr != oldPtr) // Header size is different. Prompt repointing.
{
if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, "Header size of existing file is nonstandard.", "Adjust newly packed file to have the same header size as old file? Data pointers will be updated accordingly."))
break;
// Fix pointers
byte[] update = Mini.AdjustMiniHeader(newData, oldPtr);
File.WriteAllBytes(Path.Combine(parentName, fileNum + "." + fileExt), update);
}
}
break;
}
default:
WinFormsUtil.Alert("Repacking not implemented." + Environment.NewLine + path);
return;
}
// Delete path after repacking
if (CHK_Delete.Checked && Directory.Exists(path))
Directory.Delete(path, true);
System.Media.SystemSounds.Asterisk.Play();
}
private void PB_BCLIM_Click(object sender, EventArgs e)
{
if (ModifierKeys == Keys.Control && WinFormsUtil.Prompt(MessageBoxButtons.YesNo, "Copy image to clipboard?") == DialogResult.Yes)
Clipboard.SetImage(PB_BCLIM.BackgroundImage);
else if (PB_BCLIM.BackColor == Color.Transparent)
PB_BCLIM.BackColor = Color.GreenYellow;
else PB_BCLIM.BackColor = Color.Transparent;
}
// Utility
private readonly Size CLIMWindow;
private void B_Reset_Click(object sender, EventArgs e)
{
PB_BCLIM.Size = CLIMWindow;
}
private static void BatchRenameExtension(string Folder)
{
if (!Directory.Exists(Folder))
return;
foreach (string f in Directory.GetFiles(Folder, "*", SearchOption.AllDirectories))
{
try
{
string ext = Path.GetExtension(f);
string newExt = FileFormat.Guess(f);
if (ext != newExt)
File.Move(f, Path.Combine(Path.GetDirectoryName(f), Path.GetFileNameWithoutExtension(f)) + newExt);
}
catch { }
}
}
private void CloseForm(object sender, FormClosingEventArgs e)
{
if (threads > 0 && DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, "Currently processing files.", "Abort?"))
e.Cancel = true;
}
}