Update AudioExtractor.java
parent
80760bc534
commit
21efae2c2a
|
@ -38,63 +38,6 @@ public class AudioExtractor {
|
||||||
*/
|
*/
|
||||||
private final static boolean CMD_MODE = true;
|
private final static boolean CMD_MODE = true;
|
||||||
|
|
||||||
/**
|
|
||||||
* ANSI code to go up one line, followed by a carriage return.
|
|
||||||
* Useful for overwriting user input.
|
|
||||||
*/
|
|
||||||
private final static String ANSI_BACKLINE = "\033[F\r";
|
|
||||||
/**
|
|
||||||
* ANSI code to switch to beige colored text.
|
|
||||||
* Useful for highlighting user input.
|
|
||||||
*/
|
|
||||||
private final static String ANSI_BEIGE = "\u001B[93m";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hex value for the ASCII sequence "RIFF".
|
|
||||||
* Indicates the start of a RIFF header in WAV files.
|
|
||||||
*/
|
|
||||||
private final static int ASCII_RIFF = 0x5249_4646;
|
|
||||||
/**
|
|
||||||
* Hex value for the ASCII sequence "RIFF".
|
|
||||||
* Indicates the start of a RIFF header in WAV files.
|
|
||||||
*/
|
|
||||||
private final static int ASCII_WAVE = 0x5741_5645;
|
|
||||||
/**
|
|
||||||
* Hex value for the ASCII sequence "fmt ".
|
|
||||||
* Indicates the start of a format chunk in WAV files.
|
|
||||||
*/
|
|
||||||
private final static int ASCII_fmt = 0x666D_7420;
|
|
||||||
/**
|
|
||||||
* Hex value for the ASCII sequence "smpl".
|
|
||||||
* Indicates the start of a sample chunk in WAV files.
|
|
||||||
*/
|
|
||||||
private final static int ASCII_smpl = 0x736D_706C;
|
|
||||||
/**
|
|
||||||
* Hex value for the ASCII sequence "data".
|
|
||||||
* Indicates the start of a data chunk in WAV files.
|
|
||||||
*/
|
|
||||||
private final static int ASCII_data = 0x6461_7461;
|
|
||||||
/**
|
|
||||||
* Hex value for the ASCII sequence "ALIG".
|
|
||||||
* Indicates the start of an alignment chunk in WAV files.
|
|
||||||
*/
|
|
||||||
private final static int ASCII_ALIG = 0x414C_4947;
|
|
||||||
/**
|
|
||||||
* Hex value for the ASCII sequence "x2st".
|
|
||||||
* Indicates the start of an XMA stream chunk in WAV files.
|
|
||||||
*/
|
|
||||||
private final static int ASCII_x2st = 0x7832_7374;
|
|
||||||
/**
|
|
||||||
* Hex value for the ASCII sequence "seek".
|
|
||||||
* Indicates the start of an XMA stream chunk in WAV files.
|
|
||||||
*/
|
|
||||||
private final static int ASCII_seek = 0x7365_656B;
|
|
||||||
/**
|
|
||||||
* Hex value for the ASCII sequence "fLaC".
|
|
||||||
* Indicates the start of an FLAC file.
|
|
||||||
*/
|
|
||||||
private final static int ASCII_fLaC = 0x664C_6143;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BIN files must first be extracted from game disk to use this program.
|
* BIN files must first be extracted from game disk to use this program.
|
||||||
* By default uses program directory for I/O and saves audio as WAV.<br>
|
* By default uses program directory for I/O and saves audio as WAV.<br>
|
||||||
|
@ -186,6 +129,9 @@ public class AudioExtractor {
|
||||||
}
|
}
|
||||||
|
|
||||||
case "convert": {
|
case "convert": {
|
||||||
|
// File audioFile = retrieveAudio(reader);
|
||||||
|
// if(audioFile.isFile())
|
||||||
|
// convert(audioFile, encoding);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,87 +160,83 @@ public class AudioExtractor {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
System.exit(0);
|
|
||||||
|
|
||||||
if(args.length > 0) {
|
|
||||||
System.out.println("Arguments\t:");
|
|
||||||
for(int i = 0; i < args.length; i++)
|
|
||||||
System.out.print(Ansi.colorize(args[i] + ' ', Attribute.BRIGHT_GREEN_TEXT()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(args.length > 0) {
|
|
||||||
switch(args[0]) {
|
|
||||||
|
|
||||||
case "help":
|
|
||||||
if(args.length > 1)
|
|
||||||
switch(args[1]) {
|
|
||||||
case "extract":
|
|
||||||
|
|
||||||
break;
|
|
||||||
case "pack":
|
|
||||||
break;
|
|
||||||
case "print":
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
System.out.println("java -jar RR6AudioExtractor.jar [operation] [package] [extract] [option]");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "extract":
|
|
||||||
// TODO
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "pack":
|
|
||||||
// TODO
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Confirm input/output mode
|
|
||||||
boolean packMode = false;
|
|
||||||
if(args.length > 0)
|
|
||||||
packMode = args[0].equalsIgnoreCase("pack");
|
|
||||||
|
|
||||||
// Confirm package directory, else abort
|
|
||||||
File packDirectory;
|
|
||||||
if(args.length > 1)
|
|
||||||
packDirectory = Path.of(args[1]).toFile();
|
|
||||||
else packDirectory = new File(new File(AudioExtractor.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent());
|
|
||||||
if(packMode && !packDirectory.exists() && !packDirectory.mkdir()) {
|
|
||||||
System.out.println("Package output directory " + packDirectory + " does not exist, and could not be created. "
|
|
||||||
+ "Program execution aborted.");
|
|
||||||
System.exit(1);
|
|
||||||
} else if(!packMode && !packDirectory.exists()) {
|
|
||||||
System.out.println("Package input directory " + packDirectory + " does not exist. Program execution aborted.");
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Confirm extract directory, else abort
|
|
||||||
File extractDirectory;
|
|
||||||
if(args.length > 2)
|
|
||||||
extractDirectory = Path.of(args[2]).toFile();
|
|
||||||
else extractDirectory = packDirectory;
|
|
||||||
if(!packMode && !extractDirectory.exists() && !extractDirectory.mkdir()) {
|
|
||||||
System.out.println("Extract output directory " + extractDirectory + " does not exist, and could not be created. "
|
|
||||||
+ "Program execution aborted.");
|
|
||||||
System.exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Confirm BGM output format
|
|
||||||
boolean compressBGM = false;
|
|
||||||
if(args.length > 3)
|
|
||||||
compressBGM = args[3].equalsIgnoreCase("FLAC");
|
|
||||||
|
|
||||||
// Begin operation
|
|
||||||
if(packMode)
|
|
||||||
pack(packDirectory, extractDirectory);
|
|
||||||
else extract(packDirectory, extractDirectory, compressBGM);
|
|
||||||
*/
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Examines a given file and reads some of its properties.
|
||||||
|
*
|
||||||
|
* @param givenFile
|
||||||
|
*/
|
||||||
|
public static void identify(File givenFile) {
|
||||||
|
|
||||||
|
// TODO check for characteristics of BIN or WAV files
|
||||||
|
String name = givenFile.getName();
|
||||||
|
System.out.println("Name:\t" + name);
|
||||||
|
|
||||||
|
long size = givenFile.length();
|
||||||
|
System.out.println("Size:\t" + size + " Bytes" + (size > 1024 ? " (" + convertBytes(size) + ")" : "") + "\n");
|
||||||
|
|
||||||
|
// file extension
|
||||||
|
int extIndex = name.lastIndexOf('.');
|
||||||
|
String extension = null;
|
||||||
|
if(extIndex > 0 && extIndex < name.length() - 1)
|
||||||
|
extension = name.substring(extIndex + 1);
|
||||||
|
|
||||||
|
try {
|
||||||
|
RandomAccessFile file = new RandomAccessFile(givenFile, "r");
|
||||||
|
switch(extension.toLowerCase()) {
|
||||||
|
case "bin": {
|
||||||
|
LinkedHashMap<Integer, Integer> tracklist = findAudioTracks(file);
|
||||||
|
System.out.println(tracklist.size() + " Tracks (" + convertBytes(size / tracklist.size()) + " avg size)\n");
|
||||||
|
System.out.println("Format Info");
|
||||||
|
readFormatChunk(file, 0x0C);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "wav":
|
||||||
|
case "xma":
|
||||||
|
default: {
|
||||||
|
for(long i = 0; i < size - 4; i += 4) {
|
||||||
|
file.seek(i);
|
||||||
|
int value = file.readInt();
|
||||||
|
switch(value) {
|
||||||
|
case ASCII_RIFF: {
|
||||||
|
readRIFFChunk(file, (int) i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ASCII_fmt: {
|
||||||
|
i = readFormatChunk(file, (int) i) - 4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ASCII_smpl: {
|
||||||
|
i = readSampleChunk(file, (int) i) - 4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ASCII_data: {
|
||||||
|
i = readDataChunk(file, (int) i) - 4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ASCII_ALIG: {
|
||||||
|
i = readAlignmentChunk(file, (int) i) - 4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ASCII_seek: {
|
||||||
|
i = readx2stChunk(file, (int) i) - 4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ASCII_x2st: {
|
||||||
|
i = readx2stChunk(file, (int) i) - 4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println("Could not read file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts audio tracks from the given package file.
|
* Extracts audio tracks from the given package file.
|
||||||
|
@ -364,18 +306,6 @@ public class AudioExtractor {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void printProgressBar(int current, int total) {
|
|
||||||
int done = 50 * current / total;
|
|
||||||
int todo = 50 - done;
|
|
||||||
String doneStr = ""; // String.format("%" + done + "s", '█');
|
|
||||||
for(int i = 0; i < done; i++)
|
|
||||||
doneStr += '█';
|
|
||||||
String todoStr = ""; // String.format("%" + todo + "s", '_');
|
|
||||||
for(int i = 0; i < todo; i++)
|
|
||||||
doneStr += '_';
|
|
||||||
System.out.println(ANSI_BACKLINE + "Progress: |" + doneStr + todoStr + "| (" + current + "/" + total + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts audio files to the extract directory from package files located at the package directory.
|
* Extracts audio files to the extract directory from package files located at the package directory.
|
||||||
* Also supports FLAC conversion for BGM files.
|
* Also supports FLAC conversion for BGM files.
|
||||||
|
@ -383,6 +313,7 @@ public class AudioExtractor {
|
||||||
* @param packDirectory
|
* @param packDirectory
|
||||||
* @param extractDirectory
|
* @param extractDirectory
|
||||||
* @param compressBGM
|
* @param compressBGM
|
||||||
|
* @deprecated delete after adapting FLAC conversion
|
||||||
*/
|
*/
|
||||||
public static void extract(File packDirectory, File extractDirectory, boolean compressBGM) {
|
public static void extract(File packDirectory, File extractDirectory, boolean compressBGM) {
|
||||||
|
|
||||||
|
@ -515,6 +446,168 @@ public class AudioExtractor {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Packs WAV files located at the extract directory into BIN files at the package directory.
|
||||||
|
*
|
||||||
|
* @param packDirectory
|
||||||
|
* @param extractDirectory
|
||||||
|
*/
|
||||||
|
private static void pack(File packDirectory, File extractDirectory) {
|
||||||
|
|
||||||
|
// Identify extracted folders
|
||||||
|
File[] packages = new File(extractDirectory.toString()).listFiles(new FilenameFilter() {
|
||||||
|
public boolean accept(File dir, String name) {
|
||||||
|
return name.toLowerCase().startsWith("pack_") && !name.toLowerCase().endsWith(".bin");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for(int i = 0; i < packages.length; i++)
|
||||||
|
System.out.println(packages[i].getName());
|
||||||
|
|
||||||
|
for(File packDir : packages) {
|
||||||
|
|
||||||
|
// 1. Compile list of WAV files in extracted directories
|
||||||
|
File[] audioTracks = new File(packDir.toString()).listFiles(new FilenameFilter() {
|
||||||
|
public boolean accept(File dir, String name) {
|
||||||
|
return name.toLowerCase().endsWith(".wav");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Check if pack files already exist in package directory
|
||||||
|
File pack = new File(packDir.getPath() + ".bin");
|
||||||
|
if(pack.exists());
|
||||||
|
|
||||||
|
// 3. If pack file already exists, insert WAV files into existing file.
|
||||||
|
// 4. If pack file doesn't exist, create new file then insert WAV files.
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hypothesis 1: Does Ridge Racer 6 use a predetermined address list for reading tracks from package?
|
||||||
|
// Test 1: Try changing the address of a track and see if the game can handle it successfully.
|
||||||
|
|
||||||
|
// Hypothesis 2: Does Ridge Racer 6 loop tracks in their entirety, or do they use loop points defined in the file header?
|
||||||
|
// Test 1: Try editing loop information and see if it affects in-game playback.
|
||||||
|
// Test 2: Try replacing a track with a WAV file without loop data and see if it loops in in-game playback.
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patches a WAV file by seeking out its data and smpl chunks,
|
||||||
|
* replacing any existing smpl chunk with a new smpl chunk,
|
||||||
|
* setting loop points at the start and end of the data chunk.
|
||||||
|
*
|
||||||
|
* @param waveFile
|
||||||
|
*/
|
||||||
|
private static void patch(File waveFile) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
RandomAccessFile file = new RandomAccessFile(waveFile, "r");
|
||||||
|
|
||||||
|
// Also Find data location
|
||||||
|
int dataAddress = -1;
|
||||||
|
// Check if sample chunk already exists
|
||||||
|
int smplAddress = -1;
|
||||||
|
|
||||||
|
for(int i = 0; i < file.length(); i += 4) {
|
||||||
|
|
||||||
|
file.seek(i);
|
||||||
|
int scan = file.readInt();
|
||||||
|
|
||||||
|
// match "data" chunk header
|
||||||
|
if(scan == ASCII_data)
|
||||||
|
dataAddress = i;
|
||||||
|
// match "smpl" chunk header
|
||||||
|
else if(scan == ASCII_smpl)
|
||||||
|
smplAddress = i;
|
||||||
|
|
||||||
|
// if both chunks already located, stop seeking
|
||||||
|
if(smplAddress > -1 && dataAddress > -1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine size of data chunk
|
||||||
|
int dataSize;
|
||||||
|
if(dataAddress > -1)
|
||||||
|
dataSize = readChunkSize(file, dataAddress);
|
||||||
|
else throw new IOException("Data chunk not in file.");
|
||||||
|
|
||||||
|
// Determine size of smpl chunk
|
||||||
|
int smplSize;
|
||||||
|
if(smplAddress > -1)
|
||||||
|
smplSize = readChunkSize(file, smplAddress);
|
||||||
|
// smpl chunk may not already exist
|
||||||
|
|
||||||
|
// Create new sample chunk
|
||||||
|
// Insert 68-byte sample chunk with loop before data chunk
|
||||||
|
|
||||||
|
byte[] smpl = new byte[68];
|
||||||
|
// smpl (0)
|
||||||
|
smpl[0] = 0x73;
|
||||||
|
smpl[1] = 0x6D;
|
||||||
|
smpl[2] = 0x70;
|
||||||
|
smpl[3] = 0x6C;
|
||||||
|
// size (4)
|
||||||
|
smpl[4] = 0x3C;
|
||||||
|
// manufacturer (8)
|
||||||
|
// product (12)
|
||||||
|
// sample period (16)
|
||||||
|
// MIDI unity note (20)
|
||||||
|
smpl[20] = 0x3C;
|
||||||
|
// MIDI pitch fraction (24)
|
||||||
|
// SMPTE Format (28)
|
||||||
|
// SMPTE Offset (32)
|
||||||
|
// Number of Sample Loops (36)
|
||||||
|
smpl[36] = 0x01;
|
||||||
|
// Sample Loops size (40)
|
||||||
|
// loop ID (44)
|
||||||
|
// loop type (48)
|
||||||
|
// data start
|
||||||
|
// loop start (52)
|
||||||
|
// data end
|
||||||
|
// loop end (56)
|
||||||
|
// tuning fraction (60)
|
||||||
|
// play count (64)
|
||||||
|
|
||||||
|
|
||||||
|
// Set loop points to start and end of data chunk
|
||||||
|
// Update the new file size in RIFF header
|
||||||
|
|
||||||
|
} catch(IOException e) {
|
||||||
|
System.out.println("Failed to patch file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to show current progress.
|
||||||
|
* Should only be used in CMD Mode.
|
||||||
|
*
|
||||||
|
* @param current
|
||||||
|
* @param total
|
||||||
|
*/
|
||||||
|
public static void printProgressBar(int current, int total) {
|
||||||
|
|
||||||
|
int width = 50; // progress bar total width
|
||||||
|
double progress = (double) current / total; // 0 = none, 1 = complete
|
||||||
|
|
||||||
|
int progressWidth = (int) Math.floor(progress * width);
|
||||||
|
double progressRemainder = (progress * width) % 1.0;
|
||||||
|
int x = (int) Math.floor(progressRemainder * 8);
|
||||||
|
char[] c = { ' ', '▏', '▎', '▍', '▌', '▋', '▊', '▉' };
|
||||||
|
|
||||||
|
String bar = "";
|
||||||
|
for(int i = 0; i < width; i++) {
|
||||||
|
if(i < progressWidth)
|
||||||
|
bar += '█';
|
||||||
|
else if(i == progressWidth)
|
||||||
|
bar += c[x];
|
||||||
|
else bar += ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println(ANSI_BACKLINE + "Progress: |" + bar + "| (" + current + "/" + total + ")");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method to prompt user to select a package file, or folder containing one or more packages.
|
* Helper method to prompt user to select a package file, or folder containing one or more packages.
|
||||||
*
|
*
|
||||||
|
@ -637,84 +730,6 @@ public class AudioExtractor {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Examines a given file and reads some of its properties.
|
|
||||||
*
|
|
||||||
* @param givenFile
|
|
||||||
*/
|
|
||||||
public static void identify(File givenFile) {
|
|
||||||
|
|
||||||
// TODO check for characteristics of BIN or WAV files
|
|
||||||
String name = givenFile.getName();
|
|
||||||
System.out.println("Name:\t" + name);
|
|
||||||
|
|
||||||
long size = givenFile.length();
|
|
||||||
System.out.println("Size:\t" + size + " Bytes" + (size > 1024 ? " (" + convertBytes(size) + ")" : "") + "\n");
|
|
||||||
|
|
||||||
// file extension
|
|
||||||
int extIndex = name.lastIndexOf('.');
|
|
||||||
String extension = null;
|
|
||||||
if(extIndex > 0 && extIndex < name.length() - 1)
|
|
||||||
extension = name.substring(extIndex + 1);
|
|
||||||
|
|
||||||
try {
|
|
||||||
RandomAccessFile file = new RandomAccessFile(givenFile, "r");
|
|
||||||
switch(extension.toLowerCase()) {
|
|
||||||
case "bin": {
|
|
||||||
LinkedHashMap<Integer, Integer> tracklist = findAudioTracks(file);
|
|
||||||
System.out.println(tracklist.size() + " Tracks (" + convertBytes(size / tracklist.size()) + " avg size)\n");
|
|
||||||
System.out.println("Format Info");
|
|
||||||
readFormatChunk(file, 0x0C);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "wav":
|
|
||||||
case "xma":
|
|
||||||
default: {
|
|
||||||
for(long i = 0; i < size - 4; i += 4) {
|
|
||||||
file.seek(i);
|
|
||||||
int value = file.readInt();
|
|
||||||
switch(value) {
|
|
||||||
case ASCII_RIFF: {
|
|
||||||
readRIFFChunk(file, (int) i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ASCII_fmt: {
|
|
||||||
i = readFormatChunk(file, (int) i) - 4;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ASCII_smpl: {
|
|
||||||
i = readSampleChunk(file, (int) i) - 4;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ASCII_data: {
|
|
||||||
i = readDataChunk(file, (int) i) - 4;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ASCII_ALIG: {
|
|
||||||
i = readAlignmentChunk(file, (int) i) - 4;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ASCII_seek: {
|
|
||||||
i = readx2stChunk(file, (int) i) - 4;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ASCII_x2st: {
|
|
||||||
i = readx2stChunk(file, (int) i) - 4;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
System.out.println("Could not read file.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if RIFF header
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to read RIFF header information.
|
* Helper function to read RIFF header information.
|
||||||
*
|
*
|
||||||
|
@ -1046,138 +1061,7 @@ public class AudioExtractor {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Packs WAV files located at the extract directory into BIN files at the package directory.
|
|
||||||
*
|
|
||||||
* @param packDirectory
|
|
||||||
* @param extractDirectory
|
|
||||||
*/
|
|
||||||
private static void pack(File packDirectory, File extractDirectory) {
|
|
||||||
|
|
||||||
// Identify extracted folders
|
|
||||||
File[] packages = new File(extractDirectory.toString()).listFiles(new FilenameFilter() {
|
|
||||||
public boolean accept(File dir, String name) {
|
|
||||||
return name.toLowerCase().startsWith("pack_") && !name.toLowerCase().endsWith(".bin");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for(int i = 0; i < packages.length; i++)
|
|
||||||
System.out.println(packages[i].getName());
|
|
||||||
|
|
||||||
for(File packDir : packages) {
|
|
||||||
|
|
||||||
// 1. Compile list of WAV files in extracted directories
|
|
||||||
File[] audioTracks = new File(packDir.toString()).listFiles(new FilenameFilter() {
|
|
||||||
public boolean accept(File dir, String name) {
|
|
||||||
return name.toLowerCase().endsWith(".wav");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 2. Check if pack files already exist in package directory
|
|
||||||
File pack = new File(packDir.getPath() + ".bin");
|
|
||||||
if(pack.exists());
|
|
||||||
|
|
||||||
// 3. If pack file already exists, insert WAV files into existing file.
|
|
||||||
// 4. If pack file doesn't exist, create new file then insert WAV files.
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hypothesis 1: Does Ridge Racer 6 use a predetermined address list for reading tracks from package?
|
|
||||||
// Test 1: Try changing the address of a track and see if the game can handle it successfully.
|
|
||||||
|
|
||||||
// Hypothesis 2: Does Ridge Racer 6 loop tracks in their entirety, or do they use loop points defined in the file header?
|
|
||||||
// Test 1: Try editing loop information and see if it affects in-game playback.
|
|
||||||
// Test 2: Try replacing a track with a WAV file without loop data and see if it loops in in-game playback.
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Patches a WAV file by seeking out its data and smpl chunks,
|
|
||||||
* replacing any existing smpl chunk with a new smpl chunk,
|
|
||||||
* setting loop points at the start and end of the data chunk.
|
|
||||||
*
|
|
||||||
* @param waveFile
|
|
||||||
*/
|
|
||||||
private static void patch(File waveFile) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
RandomAccessFile file = new RandomAccessFile(waveFile, "r");
|
|
||||||
|
|
||||||
// Also Find data location
|
|
||||||
int dataAddress = -1;
|
|
||||||
// Check if sample chunk already exists
|
|
||||||
int smplAddress = -1;
|
|
||||||
|
|
||||||
for(int i = 0; i < file.length(); i += 4) {
|
|
||||||
|
|
||||||
file.seek(i);
|
|
||||||
int scan = file.readInt();
|
|
||||||
|
|
||||||
// match "data" chunk header
|
|
||||||
if(scan == ASCII_data)
|
|
||||||
dataAddress = i;
|
|
||||||
// match "smpl" chunk header
|
|
||||||
else if(scan == ASCII_smpl)
|
|
||||||
smplAddress = i;
|
|
||||||
|
|
||||||
// if both chunks already located, stop seeking
|
|
||||||
if(smplAddress > -1 && dataAddress > -1)
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine size of data chunk
|
|
||||||
int dataSize;
|
|
||||||
if(dataAddress > -1)
|
|
||||||
dataSize = readChunkSize(file, dataAddress);
|
|
||||||
else throw new IOException("Data chunk not in file.");
|
|
||||||
|
|
||||||
// Determine size of smpl chunk
|
|
||||||
int smplSize;
|
|
||||||
if(smplAddress > -1)
|
|
||||||
smplSize = readChunkSize(file, smplAddress);
|
|
||||||
// smpl chunk may not already exist
|
|
||||||
|
|
||||||
// Create new sample chunk
|
|
||||||
// Insert 68-byte sample chunk with loop before data chunk
|
|
||||||
|
|
||||||
byte[] smpl = new byte[68];
|
|
||||||
// smpl (0)
|
|
||||||
smpl[0] = 0x73;
|
|
||||||
smpl[1] = 0x6D;
|
|
||||||
smpl[2] = 0x70;
|
|
||||||
smpl[3] = 0x6C;
|
|
||||||
// size (4)
|
|
||||||
smpl[4] = 0x3C;
|
|
||||||
// manufacturer (8)
|
|
||||||
// product (12)
|
|
||||||
// sample period (16)
|
|
||||||
// MIDI unity note (20)
|
|
||||||
smpl[20] = 0x3C;
|
|
||||||
// MIDI pitch fraction (24)
|
|
||||||
// SMPTE Format (28)
|
|
||||||
// SMPTE Offset (32)
|
|
||||||
// Number of Sample Loops (36)
|
|
||||||
smpl[36] = 0x01;
|
|
||||||
// Sample Loops size (40)
|
|
||||||
// loop ID (44)
|
|
||||||
// loop type (48)
|
|
||||||
// data start
|
|
||||||
// loop start (52)
|
|
||||||
// data end
|
|
||||||
// loop end (56)
|
|
||||||
// tuning fraction (60)
|
|
||||||
// play count (64)
|
|
||||||
|
|
||||||
|
|
||||||
// Set loop points to start and end of data chunk
|
|
||||||
// Update the new file size in RIFF header
|
|
||||||
|
|
||||||
} catch(IOException e) {
|
|
||||||
System.out.println("Failed to patch file.");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to prompt user to select a WAV file.
|
* Helper function to prompt user to select a WAV file.
|
||||||
|
@ -2443,4 +2327,63 @@ public class AudioExtractor {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ANSI code to go up one line, followed by a carriage return.
|
||||||
|
* Useful for overwriting user input.
|
||||||
|
*/
|
||||||
|
private final static String ANSI_BACKLINE = "\033[F\r";
|
||||||
|
/**
|
||||||
|
* ANSI code to switch to beige colored text.
|
||||||
|
* Useful for highlighting user input.
|
||||||
|
*/
|
||||||
|
private final static String ANSI_BEIGE = "\u001B[93m";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hex value for the ASCII sequence "RIFF".
|
||||||
|
* Indicates the start of a RIFF header in WAV files.
|
||||||
|
*/
|
||||||
|
private final static int ASCII_RIFF = 0x5249_4646;
|
||||||
|
/**
|
||||||
|
* Hex value for the ASCII sequence "RIFF".
|
||||||
|
* Indicates the start of a RIFF header in WAV files.
|
||||||
|
*/
|
||||||
|
private final static int ASCII_WAVE = 0x5741_5645;
|
||||||
|
/**
|
||||||
|
* Hex value for the ASCII sequence "fmt ".
|
||||||
|
* Indicates the start of a format chunk in WAV files.
|
||||||
|
*/
|
||||||
|
private final static int ASCII_fmt = 0x666D_7420;
|
||||||
|
/**
|
||||||
|
* Hex value for the ASCII sequence "smpl".
|
||||||
|
* Indicates the start of a sample chunk in WAV files.
|
||||||
|
*/
|
||||||
|
private final static int ASCII_smpl = 0x736D_706C;
|
||||||
|
/**
|
||||||
|
* Hex value for the ASCII sequence "data".
|
||||||
|
* Indicates the start of a data chunk in WAV files.
|
||||||
|
*/
|
||||||
|
private final static int ASCII_data = 0x6461_7461;
|
||||||
|
/**
|
||||||
|
* Hex value for the ASCII sequence "ALIG".
|
||||||
|
* Indicates the start of an alignment chunk in WAV files.
|
||||||
|
*/
|
||||||
|
private final static int ASCII_ALIG = 0x414C_4947;
|
||||||
|
/**
|
||||||
|
* Hex value for the ASCII sequence "x2st".
|
||||||
|
* Indicates the start of an XMA stream chunk in WAV files.
|
||||||
|
*/
|
||||||
|
private final static int ASCII_x2st = 0x7832_7374;
|
||||||
|
/**
|
||||||
|
* Hex value for the ASCII sequence "seek".
|
||||||
|
* Indicates the start of an XMA stream chunk in WAV files.
|
||||||
|
*/
|
||||||
|
private final static int ASCII_seek = 0x7365_656B;
|
||||||
|
/**
|
||||||
|
* Hex value for the ASCII sequence "fLaC".
|
||||||
|
* Indicates the start of an FLAC file.
|
||||||
|
*/
|
||||||
|
private final static int ASCII_fLaC = 0x664C_6143;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue