commit
3a781245e6
|
@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue