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