private void LoadSpc(string path) { currentSPC = new SpcFile(); currentSPCFilename = path; currentSPC.Load(currentSPCFilename); statusText.Text = $"{currentSPCFilename} loaded."; SubFileListView.ItemsSource = currentSPC.Subfiles; InitFileSystemWatcher(); }
/// <summary> /// Clears the contents of <see cref="ArchiveListView"/> and repopulates it with the directories and files in the provided directory. /// </summary> /// <param name="file">The archive file whose contents will populate the view.</param> public void PopulateArchiveListView(string file) { CurrentArchiveFileInfo = new FileInfo(file); SpcFile tempSpc = new SpcFile(); tempSpc.Load(CurrentArchiveFileInfo.FullName); ArchiveListView.Items.Clear(); foreach (var subfile in tempSpc.Subfiles) { TextBlock tb = new TextBlock(); tb.Text = subfile.Name; tb.MouseRightButtonDown += GenerateSubfileContextMenu; ArchiveListView.Items.Add(tb); } }
/// <summary> /// Processes a previously-extracted subfile that's open in an editor. This process runs its translation steps backwards, /// before finally re-packing the final product into the original SPC file it came from. /// </summary> /// <param name="tempDir">The temporary directory where the extracted subfile data is located.</param> /// <param name="info">The <see cref="EditorTrackingInfo"/> associated with the editor and subfile.</param> private void RepackArchiveSubfile(string tempDir, EditorTrackingInfo info) { // First, run all translation steps in reverse var translationSteps = info.SelectedAssociation.TranslationSteps; var translatedFilenames = info.SubfileNameHistory; for (int step = (translationSteps.Count - 1); step >= 0; --step) { // Resolve internal/external translator object resolvedTranslator = Config.FileAssociationConfig.ResolveInternalExternal(translationSteps[step]) as Process; // Run the translation step if (resolvedTranslator is Window) { Window translatorWindow = resolvedTranslator as Window; // Finally, open the target editor window as blocking translatorWindow.ShowDialog(); } else if (resolvedTranslator is Process) { Process translatorProcess = resolvedTranslator as Process; // Setup the target process' launch args translatorProcess.StartInfo.Arguments = Path.Combine(tempDir, translatedFilenames[step + 1]); translatorProcess.Start(); translatorProcess.WaitForExit(); } else { // If we get here, there's been an error and we should abort return; } } // The final translated filename is the original file & format, repack it SpcFile spc = new SpcFile(); spc.Load(info.OriginArchivePath); string fullSubfilePath = Path.Combine(tempDir, translatedFilenames[0]); spc.InsertSubfile(fullSubfilePath); spc.Save(info.OriginArchivePath); }
static void Main(string[] args) { Console.WriteLine("SPC Tool by CaptainSwag101\n" + "Version 1.1.0, built on 2020-10-15\n"); // Setup text encoding so we can use Shift-JIS text later on Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); // Ensure we actually have some arguments, if not, print usage string if (args.Length == 0) { Console.WriteLine("Usage: SpcTool.exe <SPC file> (optional auto-execute commands and parameters, encapsulated in {})"); Console.WriteLine("Example: SpcTool.exe test.spc {extract} {file1.srd *.txt} {insert} {file4.dat} {exit}"); return; } // Parse input arguments // If the first argument is a valid SPC file (and if we reach this point it probably is), load it. string loadedSpcName = args[0]; FileInfo loadedSpcInfo = new FileInfo(loadedSpcName); if (!loadedSpcInfo.Exists) { Console.WriteLine($"ERROR: \"{loadedSpcName}\" does not exist."); return; } if (loadedSpcInfo.Extension.ToLowerInvariant() != ".spc") { Console.WriteLine("WARNING: Input file does not have the \".spc\" extension.\nIf you experience any issues, it means this file probably isn't an SPC archive."); } SpcFile loadedSpc = new SpcFile(); loadedSpc.Load(loadedSpcName); // Combine any remaining args into a single long string to be broken down by our regex Queue <string>?autoExecQueue = null; if (args.Length > 1) { autoExecQueue = new Queue <string>(); StringBuilder remainingArgsBuilder = new StringBuilder(); remainingArgsBuilder.AppendJoin(" ", args[1..args.Length]);
private void OpenSelectedArchiveSubfile(object sender, int associationIndex = 0) { if (sender == null || !(sender is ListView)) { return; } if (!((sender as ListView).SelectedItem is TextBlock selectedTextBlock)) { return; } // Open an editor based on target file type string targetFileExt = Path.GetExtension(selectedTextBlock.Text).ToLowerInvariant(); // Throw an error if we can't find a valid editor if (!Config.FileAssociationConfig.AssociationList.ContainsKey(targetFileExt)) { throw new NotImplementedException($"The filetype {targetFileExt} is not supported."); } // Create the inner temp directory //string trackingHash = (CurrentArchivePath + originTextBlock.Text).GetHashCode().ToString("X8"); string trackingHash = Path.GetRandomFileName(); string generatedTempDir = Path.Combine(AppTempDirInfo.FullName, trackingHash); //string tempFileLocation = Path.Combine(generatedTempDir, originTextBlock.Text); Directory.CreateDirectory(generatedTempDir); // Extract the subfile to a temporary folder SpcFile temp = new SpcFile(); temp.Load(CurrentArchiveFileInfo.FullName); temp.ExtractSubfile(selectedTextBlock.Text, generatedTempDir); // Default to the first association in the list var association = Config.FileAssociationConfig.AssociationList[targetFileExt][associationIndex]; // Process translation steps, if any List <string> translatedOutputHistory = new List <string>(); translatedOutputHistory.Add(selectedTextBlock.Text); foreach (string translationStep in association.TranslationSteps) { object resolvedTranslator = Config.FileAssociationConfig.ResolveInternalExternal(translationStep) as Process; // Run the translation step if (resolvedTranslator is Window) { Window translatorWindow = resolvedTranslator as Window; // Finally, open the target editor window as blocking translatorWindow.ShowDialog(); } else if (resolvedTranslator is Process) { Process translatorProcess = resolvedTranslator as Process; // Setup the target process' launch args translatorProcess.StartInfo.Arguments = Path.Combine(generatedTempDir, translatedOutputHistory.Last()); translatorProcess.Start(); translatorProcess.WaitForExit(); } else { // If we get here, there's been an error and we should abort and clean up Directory.Delete(generatedTempDir, true); } // Check for any extra files and use the first that isn't our starting file as the translated output List <string> newFiles = new List <string>(); foreach (var file in Directory.EnumerateFiles(generatedTempDir)) { FileInfo tempInfo = new FileInfo(file); if (!translatedOutputHistory.Contains(tempInfo.Name)) { newFiles.Add(tempInfo.Name); } } // TODO: If multiple new files are generated, prompt the user for which file to use as the output foreach (string newFile in newFiles) { translatedOutputHistory.Add(newFile); break; } } // Get editor window if association is internal, launch external program otherwise object resolvedEditor = Config.FileAssociationConfig.ResolveInternalExternal(association.EditorProgram); // Add the target editor to our tracking database (use the non-translated subfile name here!!!) ActiveFileDatabase.Add(generatedTempDir, new EditorTrackingInfo { Editor = resolvedEditor, OriginArchivePath = CurrentArchiveFileInfo.FullName, SubfileNameHistory = translatedOutputHistory, SelectedAssociation = association }); if (resolvedEditor is Window && resolvedEditor is IFileEditor) { Window editorWindow = resolvedEditor as Window; // Subscribe to the editor's closed event so we can rebuild its target archive and clean up any temporary files editorWindow.Closed += OnEditorWindowClosed; // Open the target file's data in the editor (resolvedEditor as IFileEditor).LoadFile(Path.Combine(generatedTempDir, translatedOutputHistory.Last())); // Finally, open the target editor window editorWindow.Show(); } else if (resolvedEditor is Process) { Process editorProcess = resolvedEditor as Process; // Subscribe to the editor's exit event so we can rebuild its target archive and clean up any temporary files editorProcess.EnableRaisingEvents = true; editorProcess.Exited += OnEditorProcessExited; // Setup the target process' launch args editorProcess.StartInfo.Arguments = generatedTempDir + Path.DirectorySeparatorChar + translatedOutputHistory.Last(); // Finally, open the target editor window editorProcess.Start(); } else { // If we get here, there's been an error and we should abort and clean up Directory.Delete(generatedTempDir, true); ActiveFileDatabase.Remove(generatedTempDir); } }
static void Main(string[] args) { Console.WriteLine("SPC Tool by CaptainSwag101\n" + "Version 0.0.2, built on 2019-09-25\n"); // Parse input argument if (args.Length == 0) { return; } FileInfo info = new FileInfo(args[0]); if (!info.Exists) { Console.WriteLine($"ERROR: \"{args[0]}\" does not exist."); return; } if (info.Extension.ToLowerInvariant() != ".spc") { Console.WriteLine("ERROR: Input file does not have the \".spc\" extension."); return; } // If the first argument is a valid SPC file (and if we reach this point it probably is), it is the input. input = args[0]; // Parse operation argument // If command starts with "--", it is our operation to perform if (args.Length > 1 && args[1].StartsWith("--")) { string op = args[1].TrimStart('-').ToLowerInvariant(); if (validOperations.Keys.Any(op.Contains)) { operation = op; } else { Console.WriteLine("ERROR: Invalid operation specified."); return; } } // Parse target arguments for (int i = 2; i < args.Length; ++i) { targets.Add(args[i]); } // Load the input file SpcFile spc = new SpcFile(); spc.Load(input); // Execute operation if (operation == null) { Console.WriteLine("ERROR: No operation specified."); } else if (validOperations[operation] != -1 && targets.Count != validOperations[operation]) { Console.WriteLine($"ERROR: Invalid number of target(s) specified, expected {validOperations[operation]}."); } else { switch (operation) { case "list": case "bench": Console.WriteLine($"\"{info.Name}\" contains the following subfiles:\n"); foreach (SpcSubfile subfile in spc.Subfiles) { Console.WriteLine($"Subfile name: \"{subfile.Name}\""); Console.WriteLine($"\tCompression flag: {subfile.CompressionFlag}"); Console.WriteLine($"\tUnknown flag: {subfile.UnknownFlag}"); Console.WriteLine($"\tCurrent size: {subfile.CurrentSize.ToString("n0")} bytes"); Console.WriteLine($"\tOriginal size: {subfile.OriginalSize.ToString("n0")} bytes"); // Benchmark decompression and compression if (operation == "bench") { Stopwatch stopwatch = new Stopwatch(); Console.Write("Decompressing..."); stopwatch.Start(); subfile.Decompress(); stopwatch.Stop(); Console.WriteLine($" Done! Took {stopwatch.Elapsed.ToString()}"); Console.Write("Compressing..."); stopwatch.Restart(); subfile.Compress(); stopwatch.Stop(); Console.WriteLine($" Done! Took {stopwatch.Elapsed.ToString()}"); } Console.WriteLine(); } break; case "extract": // Setup an output directory for extracted files output ??= info.DirectoryName + Path.DirectorySeparatorChar + info.Name.Substring(0, info.Name.Length - info.Extension.Length); Directory.CreateDirectory(output); // Generate list of subfiles to be extracted that match the target regex List <string> subfilesToExtract = new List <string>(); foreach (string target in targets) { string regexTarget = "^" + Regex.Escape(target).Replace("\\?", ".").Replace("\\*", ".*") + "$"; foreach (SpcSubfile subfile in spc.Subfiles) { if (Regex.IsMatch(subfile.Name, regexTarget)) { subfilesToExtract.Add(subfile.Name); } } } // Extract the subfiles using Tasks Task[] extractTasks = new Task[subfilesToExtract.Count]; // IMPORTANT: If we ever switch to a for loop instead of foreach, // make sure to make a local scoped copy of the subfile name in order to prevent // threading weirdness from passing the wrong string value and causing random issues. foreach (string subfileName in subfilesToExtract) { Console.WriteLine($"Extracting \"{subfileName}\"..."); extractTasks[subfilesToExtract.IndexOf(subfileName)] = Task.Factory.StartNew(() => spc.ExtractSubfile(subfileName, output)); } // Wait until all target subfiles have been extracted Task.WaitAll(extractTasks); break; case "insert": // Insert the subfiles using Tasks Task[] insertTasks = new Task[targets.Count]; // IMPORTANT: If we ever switch to a for loop instead of foreach, // make sure to make a local scoped copy of the subfile name in order to prevent // threading weirdness from passing the wrong string value and causing random issues. foreach (string subfileName in targets) { Console.WriteLine($"Inserting \"{subfileName}\"..."); insertTasks[targets.IndexOf(subfileName)] = spc.InsertSubfileAsync(subfileName, confirmation: ConfirmOverwrite); } // Wait until all target subfiles have been inserted foreach (Task task in insertTasks) { task.Start(); task.Wait(); } // Save the spc file spc.Save(input); break; } } if (pauseAfterComplete) { Console.WriteLine("Press Enter to close..."); Console.Read(); } }