mirror of
https://github.com/codestation/mhtools
synced 2024-11-10 05:44:17 +00:00
Added support for decrypt and extract quest files.
Added support for extract/rebuild string tables from mib files.
This commit is contained in:
parent
29925a5ce9
commit
d88317ebd2
5 changed files with 163 additions and 58 deletions
6
README
6
README
|
@ -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.
|
||||
|
|
|
@ -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")) {
|
||||
|
|
|
@ -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[]) {
|
||||
|
|
|
@ -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 + "/"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue