Added support for decrypt and extract quest files.

Added support for extract/rebuild string tables from mib files.
This commit is contained in:
codestation 2011-07-23 21:38:42 -04:30
parent 29925a5ce9
commit d88317ebd2
5 changed files with 163 additions and 58 deletions

6
README
View file

@ -12,6 +12,8 @@ encoder/decoder number meaning to use in binary files:
2 -> 5311-5323
3 -> 5370-5373
7 -> mib quests
To unpack and decrypt all the files from a container, e.g.:
java -jar mhtrans.jar --dec-all /home/user/data.bin output_dir
@ -51,7 +53,7 @@ java -jar mhtrans.jar --rebuild /home/user/unpacked_countainer_dir 5
To unpack a .pak file
java -jar mhtrans.jar --extract file.pak 6
The KIRK related functions are untested and requires the Bouncy Castle Crypto API
The KIRK related functions requires the Bouncy Castle Crypto API
that can be downloaded from http://www.bouncycastle.org/download/bcprov-jdk16-146.jar
The PSP Crypto engine and AES wrapper comes from the Jpcsp project.
The PSP Crypto engine and AES wrapper comes from the Jpcsp project.

View file

@ -53,10 +53,13 @@ public class Mhtrans {
dec = new ExtractPluginA();
break;
case 2:
dec = new ExtractPluginB(false);
dec = new ExtractPluginB(0);
break;
case 4:
dec = new ExtractPluginB(true);
dec = new ExtractPluginB(1);
break;
case 7:
dec = new ExtractPluginB(2);
break;
case 3:
dec = new ExtractPluginC();
@ -91,6 +94,7 @@ public class Mhtrans {
enc = new RebuildPluginB(0);
break;
case 4:
case 7:
enc = new RebuildPluginB(type);
break;
case 3:
@ -128,6 +132,7 @@ public class Mhtrans {
System.err.println(" java -jar mhtrans.jar --create-patch <xxxx.bin.enc> [ ... <xxxx.bin.enc>] <output_file>");
System.err.println(" java -jar mhtrans.jar --decrypt-quest <mxxxxx.mib>");
System.err.println(" java -jar mhtrans.jar --encrypt-quest <mxxxxx.mib>");
System.err.println(" java -jar mhtrans.jar --extract-quests <xxxxxx.bin>");
System.err.println(" java -jar mhtrans.jar --update-sha1 <mxxxxx.mib>");
System.err.println(" java -jar mhtrans.jar --decrypt-save <xxxxx.bin>");
System.err.println(" java -jar mhtrans.jar --encrypt-save <xxxxx.bin>");
@ -188,6 +193,8 @@ public class Mhtrans {
new QuestCypher().encrypt(args[1]);
} else if(args[0].equals("--decrypt-quest")) {
new QuestCypher().decrypt(args[1]);
} else if(args[0].equals("--extract-quests")) {
new QuestCypher().extract(args[1]);
} else if(args[0].equals("--update-sha1")) {
new QuestCypher().update_sha1(args[1]);
} else if(args[0].equals("--encrypt-save")) {

View file

@ -28,6 +28,7 @@ import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import keys.QuestKeys;
@ -35,6 +36,9 @@ public class QuestCypher implements QuestKeys {
private short key_var_table[] = {0, 0, 0, 0};
private final int QUEST_SIZE = 24592;
private final int QUEST_LAST = 2496;
public boolean decrypt(String filein) {
try {
File fd = new File(filein);
@ -48,11 +52,11 @@ public class QuestCypher implements QuestKeys {
short short_bt[] = new short[byte_bt.length/2];
sb.get(short_bt);
System.out.println("Decrypting quest file");
decrypt_quest(short_bt);
int len = decrypt_quest(short_bt);
sb.rewind();
sb.put(short_bt);
FileOutputStream out = new FileOutputStream(filein + ".dec");
out.write(byte_bt);
out.write(byte_bt, 0, len);
out.close();
System.out.println("finished.");
} catch (FileNotFoundException e) {
@ -92,14 +96,67 @@ public class QuestCypher implements QuestKeys {
}
return true;
}
public boolean extract(String filein) {
try {
File fd = new File(filein);
if(fd.length() != QUEST_SIZE * 100 + QUEST_LAST) {
System.err.println("Invalid file size");
return false;
}
String filename = new File(filein).getName();
String directory = filename.split("\\.")[0];
new File(directory).mkdir();
FileInputStream in = new FileInputStream(fd);
byte byte_bt[] = new byte[(int)fd.length()];
in.read(byte_bt);
in.close();
for(int i = 0; i < 100; i++) {
ByteBuffer bt = ByteBuffer.wrap(byte_bt,i * QUEST_SIZE, QUEST_SIZE);
bt.order(ByteOrder.LITTLE_ENDIAN);
ShortBuffer sb = bt.asShortBuffer();
short short_bt[] = new short[QUEST_SIZE/2];
sb.get(short_bt);
System.out.println("Decrypting quest file # " + i);
int len = decrypt_quest(short_bt);
if(len > 0) {
sb.rewind();
sb.put(short_bt);
String outname = String.format("%s/%02d_quest.bin", directory, i);
FileOutputStream out = new FileOutputStream(outname);
out.write(byte_bt, i * QUEST_SIZE, len);
out.close();
} else {
System.out.println("The quest # " + i + " is empty");
}
}
System.out.println("Writting enddata.bin");
String outname = String.format("%s/enddata.bin", directory);
FileOutputStream out = new FileOutputStream(outname);
out.write(byte_bt, 100 * QUEST_SIZE, QUEST_LAST);
out.close();
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
private void decrypt_quest(short pData[]) {
calc_key(key_var_table, pData);
private int decrypt_quest(short pData[]) {
// check if is a empty quest
short check[] = new short[8];
System.arraycopy(pData, 0, check, 0, 8);
if(Arrays.equals(check, new short[8])) {
return 0;
}
calc_key(key_var_table, pData);
for(int i = 8; i < pData.length*2; i += 2) {
int index = (i >> 1);
int key = get_xor_key(key_var_table, index & 0x03);
pData[index] ^= (short)(key & 0xFFFF);
}
}
return (int)pData[4] + (int)(pData[5] << 16) + 0x20;
}
private void encrypt_quest(short pData[]) {

View file

@ -39,9 +39,19 @@ import base.HelperDec;
public class ExtractPluginB extends HelperDec implements Decoder {
private int mhp3_skip_bytes;
private int mhp3_seek_skip;
public ExtractPluginB(boolean newdec) {
mhp3_skip_bytes = newdec ? 4 : 0;
public ExtractPluginB(int newdec) {
mhp3_seek_skip = 0;
switch(newdec) {
case 2:
mhp3_seek_skip = 32;
case 1:
mhp3_skip_bytes = 4;
break;
default:
mhp3_skip_bytes = 0;
}
}
@Override
@ -51,13 +61,18 @@ public class ExtractPluginB extends HelperDec implements Decoder {
try {
RandomAccessFile file = new RandomAccessFile(filename,"r");
table_offset = new Vector<Integer>();
int pointer;
while (true) {
pointer = readInt(file);
if (pointer == 0) {
break;
}
table_offset.add(pointer);
if(mhp3_seek_skip != 0) {
file.skipBytes(mhp3_seek_skip);
table_offset.add(readInt(file));
} else {
int pointer;
while (true) {
pointer = readInt(file);
if (pointer == 0) {
break;
}
table_offset.add(pointer);
}
}
filename = new File(filename).getName();
String directory = filename.split("\\.")[0];
@ -66,7 +81,7 @@ public class ExtractPluginB extends HelperDec implements Decoder {
new File(directory + "/filelist.txt")), true, "UTF-8");
filelist.println(filename + " " + file.length());
for (int j = 0; j < table_offset.size(); j++) {
file.seek(table_offset.get(j));
file.seek(table_offset.get(j) + mhp3_seek_skip);
System.out.println("Creating " + directory + "/string_table_"
+ j + ".txt");
PrintStream stringout = new PrintStream(new FileOutputStream(
@ -87,12 +102,12 @@ public class ExtractPluginB extends HelperDec implements Decoder {
// int unknown5 = readInt(file);
// int unknown6 = readInt(file);
// int unknown7 = readInt(file);
file.seek(offset_table_pointer);
file.seek(offset_table_pointer + mhp3_seek_skip);
int string_table_pointers = readInt(file);
for (long i = string_table_pointers; i < offset_table_pointer; i += 4) {
file.seek(i);
file.seek(i + mhp3_seek_skip);
int current_string = readInt(file);
file.seek(current_string);
file.seek(current_string + mhp3_seek_skip);
String str = readString(file);
if (str.length() == 1 && str.charAt(0) == 0) {
// some offsets points to empty strings, so i put this
@ -107,20 +122,22 @@ public class ExtractPluginB extends HelperDec implements Decoder {
}
}
stringout.close();
file.seek(offset_table_pointer + 7 * 4);
file.seek((offset_table_pointer + 7 * 4) + mhp3_seek_skip);
}
if(mhp3_seek_skip == 0) {
// calculate the size of the ending unknown data and
// make a file of it
int size = (int) (file.length() - file.getFilePointer());
unknownData = new byte[size];
file.read(unknownData, 0, size);
System.out.println("Creating " + directory + "/enddata.bin");
RandomAccessFile end = new RandomAccessFile(directory
+ "/enddata.bin", "rw");
filelist.println("enddata.bin");
end.write(unknownData, 0, size);
end.setLength(end.getFilePointer());
end.close();
}
// calculate the size of the ending unknown data and
// make a file of it
int size = (int) (file.length() - file.getFilePointer());
unknownData = new byte[size];
file.read(unknownData, 0, size);
System.out.println("Creating " + directory + "/enddata.bin");
RandomAccessFile end = new RandomAccessFile(directory
+ "/enddata.bin", "rw");
filelist.println("enddata.bin");
end.write(unknownData, 0, size);
end.setLength(end.getFilePointer());
end.close();
file.close();
filelist.close();
System.out.println("Copying " + filename + " to " + directory + "/"

View file

@ -41,10 +41,20 @@ import base.HelperEnc;
*/
public class RebuildPluginB extends HelperEnc implements Encoder {
private int encoder = 0;
private int skip_bytes;
private int seek_skip;
public RebuildPluginB(int type) {
encoder = type;
seek_skip = 0;
switch(type) {
case 7:
seek_skip = 32;
case 4:
skip_bytes = 4;
break;
default:
skip_bytes = 0;
}
}
@Override
@ -65,17 +75,21 @@ public class RebuildPluginB extends HelperEnc implements Encoder {
copyfile(filepath + "/" + filename, filename + ".out");
RandomAccessFile out = new RandomAccessFile(filename + ".out", "rw");
Vector<Integer> table_offset = new Vector<Integer>();
int pointer;
while (true) {
pointer = readInt(out);
if (pointer == 0) {
break;
}
table_offset.add(pointer);
if(seek_skip != 0) {
out.skipBytes(seek_skip);
table_offset.add(readInt(out));
} else {
int pointer;
while (true) {
pointer = readInt(out);
if (pointer == 0) {
break;
}
table_offset.add(pointer);
}
}
for (int i = 0; i < table_offset.size(); i++) {
patchStringTable(filepath, filenames.get(i), out,
table_offset.get(i));
patchStringTable(filepath, filenames.get(i), out, table_offset.get(i));
}
out.close();
System.out.println("Finished!");
@ -108,22 +122,27 @@ public class RebuildPluginB extends HelperEnc implements Encoder {
} catch (EOFException e) {
}
file.close();
out.seek(starting_offset);
out.skipBytes(20 + encoder);
out.seek(starting_offset + seek_skip);
out.skipBytes(20 + skip_bytes);
int offset_table_pointer = readInt(out);
int string_start = (int) out.getFilePointer() + 28;
out.seek(offset_table_pointer);
out.seek(offset_table_pointer + seek_skip);
int string_table_pointers = readInt(out);
int diff = string_table_pointers - string_start
- calculateTotalSize(stringTable);
out.seek(string_table_pointers + seek_skip);
int string_start = readInt(out);
int total = calculateTotalSize(stringTable, 4);
int diff = string_table_pointers - string_start - total;
if (diff < 0) {
System.err.println(in + " is too big, please remove at least "
+ -diff + " bytes. Skipped");
return;
}
out.seek(string_table_pointers);
out.seek(string_table_pointers + seek_skip);
int starting_string = readInt(out);
out.seek(starting_string);
out.seek(starting_string + seek_skip);
long orig_table_pointer = string_table_pointers;
for (String str : stringTable) {
out.write(str.getBytes("UTF-8"));
@ -133,20 +152,20 @@ public class RebuildPluginB extends HelperEnc implements Encoder {
out.writeByte(0);
}
int tmp = (int) out.getFilePointer();
out.seek(string_table_pointers);
out.seek(string_table_pointers + seek_skip);
writeInt(out, starting_string);
string_table_pointers += 4;
starting_string = tmp;
starting_string = tmp - seek_skip;
out.seek(tmp);
}
long current_offset = out.getFilePointer();
long current_offset = out.getFilePointer() + seek_skip;
while (current_offset < orig_table_pointer) {
out.writeByte(0);
current_offset++;
}
}
private int calculateTotalSize(Vector<String> st)
private int calculateTotalSize(Vector<String> st, int align)
throws UnsupportedEncodingException {
int total = 0;
for (String str : st) {
@ -157,6 +176,9 @@ public class RebuildPluginB extends HelperEnc implements Encoder {
} else {
total += len + 1;
}
if(align != 0) {
total += align - (total % 4);
}
}
return total;
}