import java.util.logging.Level;
import java.io.File;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.logging.Logger;
public class OJMDumper
{
static final Logger logger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
/** the xor mask used in the M30 format */
private static final byte[] nami = new byte[]{0x6E, 0x61, 0x6D, 0x69};
/** the M30 signature, "M30\0" in little endian */
private static final int M30_SIGNATURE = 0x0030334D;
/** the OMC signature, "OMC\0" in little endian */
private static final int OMC_SIGNATURE = 0x00434D4F;
/** the OJM signature, "OJM\0" in little endian */
private static final int OJM_SIGNATURE = 0x004D4A4F;
/* this is a dump from debugging notetool */
private static final byte[] REARRANGE_TABLE = new byte[]{
0x10, 0x0E, 0x02, 0x09, 0x04, 0x00, 0x07, 0x01,
0x06, 0x08, 0x0F, 0x0A, 0x05, 0x0C, 0x03, 0x0D,
0x0B, 0x07, 0x02, 0x0A, 0x0B, 0x03, 0x05, 0x0D,
0x08, 0x04, 0x00, 0x0C, 0x06, 0x0F, 0x0E, 0x10,
0x01, 0x09, 0x0C, 0x0D, 0x03, 0x00, 0x06, 0x09,
0x0A, 0x01, 0x07, 0x08, 0x10, 0x02, 0x0B, 0x0E,
0x04, 0x0F, 0x05, 0x08, 0x03, 0x04, 0x0D, 0x06,
0x05, 0x0B, 0x10, 0x02, 0x0C, 0x07, 0x09, 0x0A,
0x0F, 0x0E, 0x00, 0x01, 0x0F, 0x02, 0x0C, 0x0D,
0x00, 0x04, 0x01, 0x05, 0x07, 0x03, 0x09, 0x10,
0x06, 0x0B, 0x0A, 0x08, 0x0E, 0x00, 0x04, 0x0B,
0x10, 0x0F, 0x0D, 0x0C, 0x06, 0x05, 0x07, 0x01,
0x02, 0x03, 0x08, 0x09, 0x0A, 0x0E, 0x03, 0x10,
0x08, 0x07, 0x06, 0x09, 0x0E, 0x0D, 0x00, 0x0A,
0x0B, 0x04, 0x05, 0x0C, 0x02, 0x01, 0x0F, 0x04,
0x0E, 0x10, 0x0F, 0x05, 0x08, 0x07, 0x0B, 0x00,
0x01, 0x06, 0x02, 0x0C, 0x09, 0x03, 0x0A, 0x0D,
0x06, 0x0D, 0x0E, 0x07, 0x10, 0x0A, 0x0B, 0x00,
0x01, 0x0C, 0x0F, 0x02, 0x03, 0x08, 0x09, 0x04,
0x05, 0x0A, 0x0C, 0x00, 0x08, 0x09, 0x0D, 0x03,
0x04, 0x05, 0x10, 0x0E, 0x0F, 0x01, 0x02, 0x0B,
0x06, 0x07, 0x05, 0x06, 0x0C, 0x04, 0x0D, 0x0F,
0x07, 0x0E, 0x08, 0x01, 0x09, 0x02, 0x10, 0x0A,
0x0B, 0x00, 0x03, 0x0B, 0x0F, 0x04, 0x0E, 0x03,
0x01, 0x00, 0x02, 0x0D, 0x0C, 0x06, 0x07, 0x05,
0x10, 0x09, 0x08, 0x0A, 0x03, 0x02, 0x01, 0x00,
0x04, 0x0C, 0x0D, 0x0B, 0x10, 0x05, 0x06, 0x0F,
0x0E, 0x07, 0x09, 0x0A, 0x08, 0x09, 0x0A, 0x00,
0x07, 0x08, 0x06, 0x10, 0x03, 0x04, 0x01, 0x02,
0x05, 0x0B, 0x0E, 0x0F, 0x0D, 0x0C, 0x0A, 0x06,
0x09, 0x0C, 0x0B, 0x10, 0x07, 0x08, 0x00, 0x0F,
0x03, 0x01, 0x02, 0x05, 0x0D, 0x0E, 0x04, 0x0D,
0x00, 0x01, 0x0E, 0x02, 0x03, 0x08, 0x0B, 0x07,
0x0C, 0x09, 0x05, 0x0A, 0x0F, 0x04, 0x06, 0x10,
0x01, 0x0E, 0x02, 0x03, 0x0D, 0x0B, 0x07, 0x00,
0x08, 0x0C, 0x09, 0x06, 0x0F, 0x10, 0x05, 0x0A,
0x04, 0x00};
public static String getType(File file) throws java.io.FileNotFoundException, java.io.IOException
{
RandomAccessFile f = null;
f = new RandomAccessFile(file,"r");
ByteBuffer buffer = f.getChannel().map(java.nio.channels.FileChannel.MapMode.READ_ONLY, 0, 4);
buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN);
byte[] signature = new byte[3];
buffer.get(signature);
return new String(signature);
}
public static void dumpFile(File file, File output_dir)
{
RandomAccessFile f = null;
try{
f = new RandomAccessFile(file,"r");
ByteBuffer buffer = f.getChannel().map(java.nio.channels.FileChannel.MapMode.READ_ONLY, 0, 4);
buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN);
int signature = buffer.getInt();
switch(signature)
{
case M30_SIGNATURE:
parseM30(f, file, output_dir);
break;
case OMC_SIGNATURE:
case OJM_SIGNATURE:
parseOMC(f, output_dir);
break;
}
f.close();
}catch(IOException e) {
logger.log(Level.WARNING, "IO expeption on file {0} : {1}", new Object[]{file.getName(), e.getMessage()});
}
}
private static void parseM30(RandomAccessFile f, File file, File out_dir) throws IOException
{
ByteBuffer buffer = f.getChannel().map(FileChannel.MapMode.READ_ONLY, 4, 28);
buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN);
// header
byte[] unk_fixed = new byte[4];
buffer.get(unk_fixed);
byte nami_encoded = buffer.get();
byte[] unk_fixed2 = new byte[3];
buffer.get(unk_fixed2);
short sample_count = buffer.getShort();
byte[] unk_fixed3 = new byte[6];
buffer.get(unk_fixed3);
int payload_size = buffer.getInt();
int unk_zero2 = buffer.getInt();
buffer = f.getChannel().map(FileChannel.MapMode.READ_ONLY, 28, payload_size);
buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN);
for(int i=0; i<sample_count; i++)
{
// reached the end of the file before the samples_count
if(buffer.remaining() < 52){
logger.log(Level.INFO, "Wrong number of samples on OJM header : {0}", file.getName());
break;
}
byte[] sample_name = new byte[32];
buffer.get(sample_name);
int sample_size = buffer.getInt();
byte unk_sample_type = buffer.get();
byte unk_off = buffer.get();
short fixed_2 = buffer.getShort();
int unk_sample_type2 = buffer.getInt();
short ref = buffer.getShort();
short unk_zero = buffer.getShort();
byte[] unk_wut = new byte[3];
buffer.get(unk_wut);
byte unk_counter = buffer.get();
byte[] sample_data = new byte[sample_size];
buffer.get(sample_data);
if(nami_encoded > 0)nami_xor(sample_data);
int value = ref;
if(unk_sample_type == 0){
value = 1000 + ref;
} else if(unk_sample_type != 5){
logger.log(Level.WARNING, "Unknown sample id type [{0}] on OJM : {1}", new Object[]{unk_sample_type, file.getName()});
}
String filename = new String(sample_name).replaceAll("\\W", "").concat("_ref("+value+").ogg");
FileOutputStream file_out = new FileOutputStream(new File(out_dir, filename));
file_out.write(sample_data);
file_out.close();
}
f.close();
}
private static void nami_xor(byte[] array)
{
for(int i=0;i+3<array.length;i+=4)
{
array[i+0] ^= nami[0];
array[i+1] ^= nami[1];
array[i+2] ^= nami[2];
array[i+3] ^= nami[3];
}
}
private static void parseOMC(RandomAccessFile f, File out_dir) throws IOException
{
ByteBuffer buffer = f.getChannel().map(java.nio.channels.FileChannel.MapMode.READ_ONLY, 4, 16);
buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN);
short unk1 = buffer.getShort();
short unk2 = buffer.getShort();
int wav_start = buffer.getInt();
int ogg_start = buffer.getInt();
int filesize = buffer.getInt();
int file_offset = 20;
int sample_id = 0; // wav samples use id 0~999
// reset global variables
acc_keybyte = 0xFF;
acc_counter = 0;
while(file_offset < ogg_start) // WAV data
{
buffer = f.getChannel().map(java.nio.channels.FileChannel.MapMode.READ_ONLY, file_offset, 56);
buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN);
file_offset += 56;
byte[] sample_name = new byte[32];
buffer.get(sample_name);
short audio_format = buffer.getShort();
short num_channels = buffer.getShort();
int sample_rate = buffer.getInt();
int bit_rate = buffer.getInt();
short block_align = buffer.getShort();
short bits_per_sample = buffer.getShort();
int data = buffer.getInt();
int chunk_size = buffer.getInt();
if(chunk_size == 0){ sample_id++; continue; }
buffer = f.getChannel().map(java.nio.channels.FileChannel.MapMode.READ_ONLY, file_offset, chunk_size);
buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN);
file_offset += chunk_size;
byte[] buf = new byte[buffer.remaining()];
buffer.get(buf);
buf = rearrange(buf);
buf = acc_xor(buf);
ByteBuffer out_buffer = ByteBuffer.allocate(chunk_size+44);
out_buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN);
out_buffer.put("RIFF".getBytes());
out_buffer.putInt(chunk_size+36);
out_buffer.put("WAVE".getBytes());
out_buffer.put("fmt ".getBytes());
out_buffer.putInt(0x10); // PCM format
out_buffer.putShort(audio_format);
out_buffer.putShort(num_channels);
out_buffer.putInt(sample_rate);
out_buffer.putInt(bit_rate);
out_buffer.putShort(block_align);
out_buffer.putShort(bits_per_sample);
out_buffer.put("data".getBytes());
out_buffer.putInt(chunk_size);
out_buffer.put(buf);
String filename = new String(sample_name).replaceAll("\\W", "").concat("_ref("+sample_id+").wav");
FileOutputStream file_out = new FileOutputStream(new File(out_dir, filename));
file_out.write(out_buffer.array());
file_out.close();
sample_id++;
}
sample_id = 1000; // ogg samples use id 1000~?
byte[] tmp_buffer = new byte[1024];
while(file_offset < filesize) // OGG data
{
buffer = f.getChannel().map(java.nio.channels.FileChannel.MapMode.READ_ONLY, file_offset, 36);
buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN);
file_offset += 36;
byte[] sample_name = new byte[32];
buffer.get(sample_name);
int sample_size = buffer.getInt();
if(sample_size == 0){ sample_id++; continue; }
buffer = f.getChannel().map(java.nio.channels.FileChannel.MapMode.READ_ONLY, file_offset, sample_size);
buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN);
file_offset += sample_size;
String filename = new String(sample_name).replaceAll("\\W", "").concat("_ref("+sample_id+").ogg");
FileOutputStream file_out = new FileOutputStream(new File(out_dir, filename));
ByteBufferInputStream in = new ByteBufferInputStream(buffer);
while(true) {
int r = in.read(tmp_buffer);
if (r == -1) break;
file_out.write(tmp_buffer,0,r);
}
file_out.close();
sample_id++;
}
}
/**
* fuck the person who invented this, FUCK YOU!... but with love =$
*/
private static byte[] rearrange(byte[] buf_encoded)
{
int length = buf_encoded.length;
int key = ((length % 17) << 4) + (length % 17);
int block_size = length / 17;
// Let's fill the buffer
byte[] buf_plain = new byte[length];
System.arraycopy(buf_encoded, 0, buf_plain, 0, length);
for(int block=0;block<17;block++) // loopy loop
{
int block_start_encoded = block_size * block; // Where is the start of the enconded block
int block_start_plain = block_size * REARRANGE_TABLE[key]; // Where the final plain block will be
System.arraycopy(buf_encoded, block_start_encoded, buf_plain, block_start_plain, block_size);
key++;
}
return buf_plain;
}
/** some weird encryption */
private static int acc_keybyte = 0xFF;
private static int acc_counter = 0;
private static byte[] acc_xor(byte[] buf)
{
int temp = 0;
byte this_byte = 0;
for(int i=0;i<buf.length;i++)
{
temp = this_byte = buf[i];
if(((acc_keybyte << acc_counter) & 0x80)!=0){
this_byte = (byte) ~this_byte;
}
buf[i] = this_byte;
acc_counter++;
if(acc_counter > 7){
acc_counter = 0;
acc_keybyte = temp;
}
}
return buf;
}
}