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; } }