commit 3a781245e6dfe624a09e106f92c6222225535289 Author: Nes Date: Wed Nov 20 14:44:25 2024 -0800 Upload files to "/" diff --git a/AT3merge.java b/AT3merge.java new file mode 100644 index 0000000..b7dfce2 --- /dev/null +++ b/AT3merge.java @@ -0,0 +1,102 @@ +package goblincave.gitea.nes; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +import org.apache.commons.io.FilenameUtils; + +import io.github.jacksonbrienen.jwfd.JWindowsFileDialog; + +public class AT3merge { + + public static void main(String[] args) throws IOException { + + String[] selection = JWindowsFileDialog.showMultiDialog(null, "Select AT3 files to merge"); + boolean[] merged = new boolean[selection.length]; + + // identify AT3 part files: name_01.at3, name_02.at3 + for(int a = 0; a < selection.length; a++) { + + if(merged[a]) + continue; + + File fileA = new File(selection[a]); + String filenameA = FilenameUtils.removeExtension(fileA.getName()); + String partnameA = filenameA.substring(filenameA.length() - 3); + filenameA = filenameA.substring(0, filenameA.length() - 3); + + if(partnameA.equals("_01")) { + + for(int b = 0; b < selection.length; b++) { + + if(merged[b] || selection[a].equals(selection[b])) + continue; + + File fileB = new File(selection[b]); + + String filenameB = FilenameUtils.removeExtension(fileB.getName()); + String partnameB = filenameB.substring(filenameB.length() - 3); + filenameB = filenameB.substring(0, filenameB.length() - 3); + + if(filenameA.equals(filenameB) && partnameB.equals("_02")) { + + // MERGE parts into one file + System.out.println("Merging " + fileA.getName() + " and " + fileB.getName()); + + merge(fileA, fileB); + + merged[b] = true; + break; + + } + + } + + // REMOVE suffix from processed file + System.out.println("Renaming to " + filenameA + ".at3"); + + if(fileA.renameTo(new File(fileA.getParent(), filenameA + ".at3"))); + else System.out.println("Failed to rename " + fileA); + + merged[a] = true; + + } else continue; + + } + + // merge matching parts into a single file + // remove part suffixes from files + + } + + /** + * Appends fileB to the end of fileA. FileB is then deleted. + * + * @param fileA + * @param fileB + * @throws IOException + */ + private static void merge(File fileA, File fileB) throws IOException { + + try ( + FileInputStream input = new FileInputStream(fileB); + FileOutputStream output = new FileOutputStream(fileA, true); + ) { + // append fileB + byte[] buffer = new byte[1024]; + int bytesRead; + while((bytesRead = input.read(buffer)) != -1) { + output.write(buffer, 0, bytesRead); + } + + } + + // delete fileB + if(fileB.delete()); + else System.out.println("Failed to delete " + fileB); + + } + +} diff --git a/ATXtoAT3.java b/ATXtoAT3.java new file mode 100644 index 0000000..4962faa --- /dev/null +++ b/ATXtoAT3.java @@ -0,0 +1,115 @@ +package goblincave.gitea.nes; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.RandomAccessFile; + +import org.apache.commons.io.FilenameUtils; + +import io.github.jacksonbrienen.jwfd.FileExtension; +import io.github.jacksonbrienen.jwfd.JWindowsFileDialog; + +public class ATXtoAT3 { + + public static void main(String[] args) throws IOException { + + FileExtension atx = new FileExtension("ATX File", "atx"); + + String[] selection = JWindowsFileDialog.showMultiDialog(null, "Select ATX files to process", atx, FileExtension.ALL); + String output = JWindowsFileDialog.showDirectoryDialog(null, "Choose where to export AT3 files"); + + if(selection == null) + System.out.println("No files selected."); + + else { + for(int i = 0; i < selection.length; i++) { + + String path = selection[i]; + File file1 = new File(path); + System.out.println("Opening " + path); + int start = 0x20; // findChunk("RIFF", file, 0x10); + int end = findChunk("EOFC", file1, 0x10); + File AT3 = trim(file1, start, end, output); + + System.out.println("Saving " + AT3.toPath()); + } + } + + System.out.println(selection.length + " files exported."); + + } + + /** + * Trims input file up to start address and after end address, saving trimmed data to output path. + * + * @param input + * @param start + * @param end + * @param output + * @return trimmed file + */ + private static File trim(File input, int start, int end, String output) { + + // input validation + // start and end must be between 0 and file length + // start must be less than end + if(start < 0 || start > input.length() || end < 0 || end > input.length() || start >= end) + return null; + + File trimmed = new File(output + "/" + FilenameUtils.removeExtension(input.getName()) + ".at3"); + + try ( + FileInputStream in = new FileInputStream(input); + FileOutputStream out = new FileOutputStream(trimmed); + ) { + in.skipNBytes(start); + byte[] bytes = new byte[end - start]; + int readBytes = in.read(bytes); + out.write(bytes, 0, readBytes); + } catch (IOException e) { + e.printStackTrace(); + } + + return trimmed; + + } + + /** + * Returns the first instance of the provided key in the file. + * + * @param key + * @param file + * @param width - search address increment value. Smaller values are more thorough, but take longer to process. Adjust according to the conventions of a given file. If unsure, use a value of 1. + * @return first match + */ + public static int findChunk(String key, File file, int width) throws IOException { + + int keyValue = asciiToInt(key); + + // find address of sequence in file + try (RandomAccessFile RAF = new RandomAccessFile(file, "r")) { + for(int a = 0; a + width < file.length(); a += width) { + RAF.seek(a); + if(RAF.readInt() == keyValue) { + return a; + } + } + } + + // returns -1 if not found + return -1; + } + + /** + * Converts the given ASCII sequence to an equivalent integer value. + */ + private static int asciiToInt(String key) { + int value = 0; + for(int i = 0; i < key.length(); i++) + value += key.charAt(key.length() - 1 - i) * Math.pow(16, 2 * i); + return value; + } + +}