commit a4848cb2d42b072fac62e0550f40897e3a8ab1bc Author: codestation Date: Sun Dec 12 17:12:44 2010 +0000 first commit diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..fb50116 --- /dev/null +++ b/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..97402ea --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/index.bin +/.settings diff --git a/.project b/.project new file mode 100644 index 0000000..9d04275 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + mhtrans + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/src/base/Decoder.java b/src/base/Decoder.java new file mode 100644 index 0000000..8163d89 --- /dev/null +++ b/src/base/Decoder.java @@ -0,0 +1,88 @@ +/* MHP2GDEC v1.0 - MHP2G xxxx.bin language table extractor + Copyright (C) 2008 Codestation + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +package base; + +import java.io.EOFException; +import java.io.IOException; +import java.io.RandomAccessFile; + +public abstract class Decoder { + + public abstract void extract(String filename); + + /** + * The "readInt" function of java reads in BigEndian mode but we need + * LittleEndian so i made a custom function for that + * + * @param file + * @return 8 byte integer in LittleEndian mode + * @throws IOException + * if any error occur while reading + */ + protected int readInt(RandomAccessFile file) throws IOException, EOFException { + int ch1 = file.read(); + int ch2 = file.read(); + int ch3 = file.read(); + int ch4 = file.read(); + if ((ch1 | ch2 | ch3 | ch4) < 0) { + throw new EOFException(); + } + return (ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1 << 0); + } + + /** + * Some hex-edited files have some extra zeros at the end of the strings so + * its better to skip them + * + * @param file + * @throws IOException + * if any error occur while reading + */ + protected void advanceNext(RandomAccessFile file) throws IOException { + while (file.readByte() == 0) { + ; + } + file.seek(file.getFilePointer() - 1); + } + + /** + * The "readUTF8" function of java expects a different format of the string + * so i have to make a custom one + * + * @param file + * @return string extracted from file + * @throws IOException + * if any error occur while reading + */ + protected String readString(RandomAccessFile file) throws IOException { + byte[] buffer = new byte[1024]; + byte data = 0; + int counter = 0; + try { + do { + data = file.readByte(); + buffer[counter++] = data; + } while (data != 0); + // checks if the string is a edited one + advanceNext(file); + } catch (EOFException e) { + return null; + } + return new String(buffer, 0, counter, "UTF-8"); + } +} diff --git a/src/base/Encoder.java b/src/base/Encoder.java new file mode 100644 index 0000000..7a32bb9 --- /dev/null +++ b/src/base/Encoder.java @@ -0,0 +1,122 @@ +/* MHP2GDEC v1.0 - MHP2G xxxx.bin language table rebuilder + Copyright (C) 2008 Codestation + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +package base; + +import java.io.EOFException; +import java.io.IOException; +import java.io.RandomAccessFile; + +public abstract class Encoder { + + public abstract void compile(String filelist); + + /** + * The "readUTF8" function of java expects a different format of the string + * so i have to make a custom one. + * + * @param file + * @return string extracted from file + * @throws IOException + * if any error occur while reading + */ + protected String readString(RandomAccessFile file) throws IOException { + byte[] buffer = new byte[1024]; + byte data = 0; + boolean eol = false; + int counter = 0; + try { + while (!eol) { + switch (data = file.readByte()) { + case '\n': + eol = true; + break; + case '\r': + eol = true; + long cur = file.getFilePointer(); + if (file.readByte() != '\n') { + file.seek(cur); + eol = false; + } + break; + default: + buffer[counter++] = data; + break; + } + } + } catch (EOFException e) { + return null; + } + return new String(buffer, 0, counter, "UTF-8"); + } + + /** + * Checks if the file have the unicode BOM mark and skip it (thanks notepad + * grr..) + * + * @param file + * @throws IOException + * if any error occur while reading + */ + protected void checkUnicodeBOM(RandomAccessFile file) throws IOException { + int a = file.readByte(); + int b = file.readByte(); + int c = file.readByte(); + if (a != -17 || b != -69 || c != -65) { + file.seek(0); + } + } + + /** + * The "writeInt" function of java writes in BigEndian mode but we need + * LittleEndian so i made a custom function for that + * + * @param file + * @throws IOException + * if any error occur while writing + */ + protected void writeInt(RandomAccessFile file, int value) throws IOException { + int ch1 = (byte) (value >>> 24); + int ch2 = (byte) (value >>> 16); + int ch3 = (byte) (value >>> 8); + int ch4 = (byte) value; + file.write(ch4); + file.write(ch3); + file.write(ch2); + file.write(ch1); + } + + /** + * The "readInt" function of java reads in BigEndian mode but we need + * LittleEndian so i made a custom function for that + * + * @param file + * @return 8 byte integer in LittleEndian mode + * @throws IOException + * if any error occur while reading + */ + protected int readInt(RandomAccessFile file) throws IOException, EOFException { + int ch1 = file.read(); + int ch2 = file.read(); + int ch3 = file.read(); + int ch4 = file.read(); + if ((ch1 | ch2 | ch3 | ch4) < 0) { + throw new EOFException(); + } + return (ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1 << 0); + } +} diff --git a/src/base/Mhtrans.java b/src/base/Mhtrans.java new file mode 100644 index 0000000..92ca38d --- /dev/null +++ b/src/base/Mhtrans.java @@ -0,0 +1,184 @@ +/* MHP2GDEC v1.0 - MHP2G xxxx.bin language table extractor/rebuilder/encrypter/decrypter + Copyright (C) 2008 Codestation + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +package base; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; + +import crypt.Decrypter; +import crypt.Encrypter; +import dec.ExtractPluginA; +import dec.ExtractPluginB; +import dec.ExtractPluginC; +import enc.RebuildPluginA; +import enc.RebuildPluginB; +import enc.RebuildPluginC; + +public class Mhtrans { + + public static void extract(String filename, String decoder) { + // (00[1-2][0-9]|47[0-9][0-9])\\..* decoder A + // 53[0-9][0-9]\\..* decoder B + // 54[0-9][0-9]\\..* decoder C + + Decoder dec = null; + int type = Integer.parseInt(decoder); + switch(type) { + case 1: + dec = new ExtractPluginA(); + break; + case 2: + dec = new ExtractPluginB(false); + break; + case 4: + dec = new ExtractPluginB(true); + break; + case 3: + dec = new ExtractPluginC(); + break; + default: + System.err.println("Unknown decoder: " + decoder); + System.exit(1); + } + dec.extract(filename); + } + + public static void rebuild(String filename, String encoder) { + String str = checkFile(filename + "/filelist.txt"); + Encoder enc = null; + if (str == null) + System.exit(1); + int type = Integer.parseInt(encoder); + switch(type) { + case 1: + enc = new RebuildPluginA(); + break; + case 2: + enc = new RebuildPluginB(0); + break; + case 4: + enc = new RebuildPluginB(type); + break; + case 3: + enc = new RebuildPluginC(); + break; + default: + System.err.println("Unknown encoder: " + encoder); + System.exit(1); + } + enc.compile(filename); + } + + public static void main(String[] args) { + System.out.println("mhtrans v2.0 - MHP2G/MHFU/MHP3 xxxx.bin language table extractor/rebuilder"); + System.out.println(); + if (args.length < 2) { + System.err.println("Usage: java -jar mhtrans.jar --extract "); + System.err.println(" java -jar mhtrans.jar --rebuild "); + System.err.println(" java -jar mhtrans.jar --decrypt "); + System.err.println(" java -jar mhtrans.jar --encrypt "); + System.err.println(" java -jar mhtrans.jar --dec-ext "); + System.err.println(" java -jar mhtrans.jar --reb-enc "); + System.err.println(" java -jar mhtrans.jar --gen-index "); + System.err.println(" java -jar mhtrans.jar --dec-all "); + //System.err.println(" java MHP2GTRANS --dec-single "); + //System.err.println(" java MHP2GTRANS --insert "); + System.exit(1); + } else { + if (args[0].equals("--extract")) { + if(args.length < 3) { + System.err.println("Decoder number missing. Aborting"); + System.exit(1); + } + extract(args[1], args[2]); + } else if (args[0].equals("--rebuild")) { + if(args.length < 3) { + System.err.println("Decoder number missing. Aborting"); + System.exit(1); + } + rebuild(args[1], args[2]); + } else if (args[0].equals("--decrypt")) { + new Decrypter().decrypt(args[1], args[1] + ".dec"); + } else if (args[0].equals("--encrypt")) { + String filename = new File(args[1]).getName(); + new Encrypter().encrypt(args[1], filename + ".enc"); + } else if (args[0].equals("--dec-ext")) { + if(args.length < 3) { + System.err.println("Decoder number missing. Aborting"); + System.exit(1); + } + new Decrypter().decrypt(args[1], args[1] + ".dec"); + new File(args[1]).renameTo(new File(args[1] + ".tmp")); + new File(args[1] + ".dec").renameTo(new File(args[1])); + extract(args[1], args[2]); + new File(args[1]).delete(); + new File(args[1] + ".tmp").renameTo(new File(args[1])); + } else if (args[0].equals("--reb-enc")) { + if(args.length < 3) { + System.err.println("Decoder number missing. Aborting"); + System.exit(1); + } + rebuild(args[1], args[2]); + String filename = new File(args[1]).getName(); + new Encrypter().encrypt(filename + ".bin.out", filename + ".bin.enc"); + System.out.println("Moving to " + filename + ".bin.enc"); + new File(filename + ".bin.out").delete(); + } else if(args[0].equals("--gen-index")) { + new Decrypter().decrypt_index(args[1], null); + } else if(args[0].equals("--dec-all")) { + if(args.length < 3) { + System.err.println("Output folder missing. Aborting"); + System.exit(1); + } + new Decrypter().decrypt_whole(args[1], args[2]); +// } else if(args[0].equals("--dec-single")) { +// if(args.length < 3) { +// System.err.println("Output xxxx.bin missing. Aborting"); +// System.exit(1); +// } +// +// } else if(args[0].equals("--insert")) { +// if(args.length < 3) { +// System.err.println("Path of data.bin missing. Aborting"); +// System.exit(1); +// } + } else { + System.err.println("Unknown parameter: " + args[0]); + System.exit(1); + } + } + } + + public static String checkFile(String filename) { + try { + BufferedReader file = new BufferedReader(new FileReader(filename)); + String name = file.readLine().split(" ")[0]; + file.close(); + return name; + } catch (FileNotFoundException e) { + System.err.println(e.toString()); + return null; + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/src/crypt/DecryptTable.java b/src/crypt/DecryptTable.java new file mode 100644 index 0000000..1a825e8 --- /dev/null +++ b/src/crypt/DecryptTable.java @@ -0,0 +1,146 @@ +/* MHP2GDEC v1.0 - MH data.bin/xxxx.bin encrypter/decrypter + Copyright (C) 2008 codestation + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +package crypt; + +//import java.io.EOFException; +import java.io.EOFException; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; + +public abstract class DecryptTable { + + protected final byte decrypt_table[] = { (byte) 0xCB, (byte) 0x96, + (byte) 0x85, (byte) 0xA6, (byte) 0x5F, (byte) 0x3E, (byte) 0xAB, + (byte) 0x03, (byte) 0x50, (byte) 0xB7, (byte) 0x9C, (byte) 0x5C, + (byte) 0xB2, (byte) 0x40, (byte) 0xEF, (byte) 0xF6, (byte) 0xFF, + (byte) 0x61, (byte) 0x15, (byte) 0x29, (byte) 0xA2, (byte) 0xF1, + (byte) 0xEC, (byte) 0x52, (byte) 0x35, (byte) 0x28, (byte) 0xD9, + (byte) 0x68, (byte) 0x24, (byte) 0x36, (byte) 0xC4, (byte) 0x74, + (byte) 0x26, (byte) 0xE2, (byte) 0xD5, (byte) 0x8C, (byte) 0x47, + (byte) 0x4D, (byte) 0x2C, (byte) 0xFA, (byte) 0x86, (byte) 0x66, + (byte) 0xC1, (byte) 0x4F, (byte) 0x0B, (byte) 0x81, (byte) 0x5B, + (byte) 0x1B, (byte) 0xC0, (byte) 0x0A, (byte) 0xFD, (byte) 0x17, + (byte) 0xA4, (byte) 0xA9, (byte) 0x6D, (byte) 0x63, (byte) 0xAD, + (byte) 0xF3, (byte) 0xF4, (byte) 0x6E, (byte) 0x8D, (byte) 0x89, + (byte) 0x14, (byte) 0xDD, (byte) 0x59, (byte) 0x87, (byte) 0x4A, + (byte) 0x30, (byte) 0xCE, (byte) 0xFE, (byte) 0x3F, (byte) 0x7E, + (byte) 0x06, (byte) 0x49, (byte) 0xA5, (byte) 0x04, (byte) 0x5E, + (byte) 0xD0, (byte) 0xDE, (byte) 0xE8, (byte) 0x0F, (byte) 0xD4, + (byte) 0x13, (byte) 0x1F, (byte) 0xBA, (byte) 0xB9, (byte) 0x69, + (byte) 0x71, (byte) 0x3D, (byte) 0xE4, (byte) 0xDC, (byte) 0x58, + (byte) 0x90, (byte) 0x34, (byte) 0x3A, (byte) 0x3C, (byte) 0xCA, + (byte) 0x10, (byte) 0x76, (byte) 0xC7, (byte) 0xC8, (byte) 0x45, + (byte) 0x33, (byte) 0xC3, (byte) 0x92, (byte) 0x1D, (byte) 0x2B, + (byte) 0x1C, (byte) 0x8F, (byte) 0x6F, (byte) 0x05, (byte) 0x07, + (byte) 0x38, (byte) 0x57, (byte) 0x51, (byte) 0xD6, (byte) 0xDA, + (byte) 0x2D, (byte) 0xB3, (byte) 0xC6, (byte) 0x2E, (byte) 0x64, + (byte) 0x32, (byte) 0x1E, (byte) 0x43, (byte) 0xB1, (byte) 0x5D, + (byte) 0xE1, (byte) 0xBB, (byte) 0x8E, (byte) 0x9D, (byte) 0x72, + (byte) 0x77, (byte) 0xF2, (byte) 0x27, (byte) 0xC9, (byte) 0x7F, + (byte) 0x9E, (byte) 0xAA, (byte) 0x6A, (byte) 0x2F, (byte) 0x6C, + (byte) 0xF9, (byte) 0x48, (byte) 0xE7, (byte) 0xA0, (byte) 0x09, + (byte) 0x56, (byte) 0xB8, (byte) 0xBD, (byte) 0x20, (byte) 0x41, + (byte) 0xCD, (byte) 0x95, (byte) 0x80, (byte) 0xD7, (byte) 0x23, + (byte) 0x0C, (byte) 0x42, (byte) 0xE5, (byte) 0xAE, (byte) 0x8B, + (byte) 0x7D, (byte) 0xBC, (byte) 0x54, (byte) 0x39, (byte) 0xBF, + (byte) 0x65, (byte) 0x01, (byte) 0x88, (byte) 0xE0, (byte) 0x7B, + (byte) 0xB6, (byte) 0x16, (byte) 0x18, (byte) 0x4B, (byte) 0xCC, + (byte) 0x22, (byte) 0x5A, (byte) 0xB5, (byte) 0xEB, (byte) 0xFC, + (byte) 0xF8, (byte) 0x9B, (byte) 0x4E, (byte) 0xE6, (byte) 0xA8, + (byte) 0xBE, (byte) 0x67, (byte) 0x73, (byte) 0x97, (byte) 0x94, + (byte) 0x00, (byte) 0x62, (byte) 0xB4, (byte) 0xD2, (byte) 0x21, + (byte) 0x25, (byte) 0x11, (byte) 0x82, (byte) 0xDB, (byte) 0x93, + (byte) 0x02, (byte) 0x84, (byte) 0x7C, (byte) 0xD3, (byte) 0xB0, + (byte) 0xA3, (byte) 0x91, (byte) 0xA7, (byte) 0xF7, (byte) 0x55, + (byte) 0x70, (byte) 0x7A, (byte) 0x08, (byte) 0x75, (byte) 0x8A, + (byte) 0x53, (byte) 0x79, (byte) 0xFB, (byte) 0x9F, (byte) 0x46, + (byte) 0xF5, (byte) 0x83, (byte) 0xD8, (byte) 0x0E, (byte) 0xE9, + (byte) 0xED, (byte) 0x12, (byte) 0xD1, (byte) 0xDF, (byte) 0xF0, + (byte) 0x37, (byte) 0x2A, (byte) 0x44, (byte) 0x19, (byte) 0x9A, + (byte) 0x31, (byte) 0xCF, (byte) 0xA1, (byte) 0xAF, (byte) 0xE3, + (byte) 0x3B, (byte) 0x1A, (byte) 0x4C, (byte) 0x78, (byte) 0xC2, + (byte) 0x60, (byte) 0xEE, (byte) 0x98, (byte) 0x6B, (byte) 0x0D, + (byte) 0x99, (byte) 0xEA, (byte) 0xC5, (byte) 0xAC }; + + private long lower_offset; + private long upper_offset; + + protected void initSeed(long seed) { + lower_offset = seed & 0xFFFF; + upper_offset = seed >> 0x10 & 0xFFFF; + if (lower_offset == 0) { + lower_offset = 0x7F8D; + } + if (upper_offset == 0) { + upper_offset = 0x2345; + } + } + + protected long getBeta() { + lower_offset = (lower_offset * 0x7F8D) % 0xFFF1; + upper_offset = (upper_offset * 0x2345) % 0xFFD9; + return lower_offset + (upper_offset << 0x10); + } + + protected void set_table_value(byte table[], int pos, long value) { + table[pos] = (byte) value; + table[pos+1] = (byte) (value >> 8); + table[pos+2] = (byte) (value >> 16); + table[pos+3] = (byte) (value >> 24); + } + + protected long get_table_value(byte table[], int pos) { + return (table[pos] & 0xFF) + + ((long) (table[pos+1] & 0xFF) << 8) + + ((long) (table[pos+2] & 0xFF) << 16) + + ((long) (table[pos+3] & 0xFF) << 24); + } + + /* + * Can't use the RandomAccessFile readInt func as we need the bytes + * in reverse order + */ + private int readInt(RandomAccessFile file) throws IOException, EOFException { + int ch1 = file.read(); + int ch2 = file.read(); + int ch3 = file.read(); + int ch4 = file.read(); + if ((ch1 | ch2 | ch3 | ch4) < 0) { + throw new EOFException(); + } + return (ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1 << 0); + } + + protected int getOffset(int value) throws EOFException, FileNotFoundException, IOException { + int res = -1; + if (value == 0) { + res = 0; + } else { + RandomAccessFile table = new RandomAccessFile("index.bin", "r"); + table.seek(value * 4 - 4); + res = readInt(table); + table.close(); + } + return res; + } + + protected int extractNumber(String filename) { + return Integer.parseInt(filename.substring(filename.indexOf(".") - 4, filename.indexOf("."))); + } +} diff --git a/src/crypt/Decrypter.java b/src/crypt/Decrypter.java new file mode 100644 index 0000000..161b164 --- /dev/null +++ b/src/crypt/Decrypter.java @@ -0,0 +1,160 @@ +/* MHP2GDEC v1.0 - MH data.bin/xxxx.bin decrypter + Copyright (C) 2008 Codestation + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +package crypt; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; + +public class Decrypter extends DecryptTable { + + public void decrypt_index(String in, ByteArrayOutputStream index_buffer) { + try { + RandomAccessFile filein = new RandomAccessFile(in, "r"); + RandomAccessFile fileout = new RandomAccessFile("index.bin", "rw"); + fileout.setLength(0); + byte[] buffer = new byte[4]; + System.out.println("Decrypting index..."); + initSeed(0); + boolean table_end = false; + boolean end_flag = false; + int i = 0; + while(!table_end) { + filein.read(buffer); + buffer[0] = decrypt_table[buffer[0] & 0xFF]; + buffer[1] = decrypt_table[buffer[1] & 0xFF]; + buffer[2] = decrypt_table[buffer[2] & 0xFF]; + buffer[3] = decrypt_table[buffer[3] & 0xFF]; + long beta = getBeta(); + long alpha = get_table_value(buffer, 0); + long gamma = alpha ^ beta; + + if(gamma > 0xFF) { + end_flag = true; + } else if(end_flag) { + table_end = true; + continue; + } + set_table_value(buffer, 0, gamma); + fileout.write(buffer); + if(index_buffer != null) + index_buffer.write(buffer); + i += 4; + } + fileout.close(); + System.out.println("Index size: " + i + " bytes"); + System.out.println("File count: " + i / 4); + }catch(FileNotFoundException e) { + e.printStackTrace(); + }catch(IOException e) { + e.printStackTrace(); + } + } + + public void decrypt_whole(String in, String out) { + ByteArrayOutputStream index_buffer = new ByteArrayOutputStream(); + decrypt_index(in, index_buffer); + RandomAccessFile filein; + try { + filein = new RandomAccessFile(in, "r"); + byte index_table[] = index_buffer.toByteArray(); + int files_count = index_table.length / 4; + new File(out).mkdir(); + boolean create_subdirectory = true; + int last_subdirectory = 0; + long last_offset = 0; + for(int i = 0; i < files_count; i++) { + if(create_subdirectory) { + last_subdirectory = i / 1000; + new File(out + "/0" + Integer.toString(last_subdirectory)).mkdir(); + create_subdirectory = false; + } else { + if(last_subdirectory < i / 1000) { + create_subdirectory = true; + } + } + long offset = last_offset; + last_offset = get_table_value(index_table, i * 4); + long file_length = (get_table_value(index_table, i * 4) - offset) << 11; + String fileout = out + + "/0" + Integer.toString(last_subdirectory) + + "/" + String.format("%04d.bin", i); + System.out.print("Decrypting " + fileout + "(" + file_length + " bytes/offset: " + (offset << 11) + ") ... "); + decrypt_internal(filein, offset, file_length, fileout, false); + + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + + private void decrypt_internal(RandomAccessFile filein, long pos, long size, String out, boolean single) { + try { + if(!single) + filein.seek(pos << 11); + RandomAccessFile fileout = new RandomAccessFile(out, "rw"); + byte buffer[] = new byte[1024]; + fileout.setLength(0); + initSeed(pos); + while (size > 0) { + int read = filein.read(buffer); + size -= read; + for(int i = 0; i < read; i += 4) { + buffer[i] = decrypt_table[buffer[i] & 0xFF]; + buffer[i+1] = decrypt_table[buffer[i+1] & 0xFF]; + buffer[i+2] = decrypt_table[buffer[i+2] & 0xFF]; + buffer[i+3] = decrypt_table[buffer[i+3] & 0xFF]; + long alpha = get_table_value(buffer, i); + long beta = getBeta(); + long gamma = alpha ^ beta; + set_table_value(buffer, i, gamma); + } + fileout.write(buffer); + } + fileout.close(); + System.out.println("Finished!"); + }catch(FileNotFoundException e) { + e.printStackTrace(); + }catch(IOException e) { + e.printStackTrace(); + } + } + + public void decrypt(String in, String out) { + RandomAccessFile filein = null; + try { + filein = new RandomAccessFile(in, "r"); + System.out.print("Decrypting " + out + " ... "); + decrypt_internal(filein, getOffset(extractNumber(in)), filein.length(), out, true); + + }catch(FileNotFoundException e) { + e.printStackTrace(); + }catch(IOException e) { + e.printStackTrace(); + } finally { + if(filein != null) + try { + filein.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } +} diff --git a/src/crypt/Encrypter.java b/src/crypt/Encrypter.java new file mode 100644 index 0000000..299c99e --- /dev/null +++ b/src/crypt/Encrypter.java @@ -0,0 +1,62 @@ +/* MHP2GDEC v1.0 - MHP2G data.bin/xxxx.bin encrypter + Copyright (C) 2008 Codestation + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +package crypt; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; + +public class Encrypter extends DecryptTable { + + private byte []encrypt_table; + + public Encrypter() { + encrypt_table = new byte[256]; + for(int i = 0;i < 256; i++) { + encrypt_table[decrypt_table[i] & 0xFF] = (byte)i; + } + } + + public void encrypt(String in, String out) { + try { + RandomAccessFile filein = new RandomAccessFile(in, "r"); + RandomAccessFile fileout = new RandomAccessFile(out, "rw"); + initSeed(getOffset(extractNumber(in))); + byte[] buffer = new byte[4]; + System.out.println("Encrypting " + in); + while (filein.read(buffer) >= 0) { + long gamma = get_table_value(buffer, 0); + long beta = getBeta(); + long alpha = beta ^ gamma; + set_table_value(buffer, 0, alpha); + buffer[0] = encrypt_table[buffer[0] & 0xFF]; + buffer[1] = encrypt_table[buffer[1] & 0xFF]; + buffer[2] = encrypt_table[buffer[2] & 0xFF]; + buffer[3] = encrypt_table[buffer[3] & 0xFF]; + fileout.write(buffer); + } + filein.close(); + fileout.close(); + System.out.println("Finished!"); + }catch(FileNotFoundException e) { + e.printStackTrace(); + }catch(IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/dec/ExtractPluginA.java b/src/dec/ExtractPluginA.java new file mode 100644 index 0000000..60c0f41 --- /dev/null +++ b/src/dec/ExtractPluginA.java @@ -0,0 +1,120 @@ +/* MHP2GDEC v1.0 - MHP2G 0016/0017/475x.bin language table extractor + Copyright (C) 2008-2010 Codestation + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +package dec; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.io.RandomAccessFile; + +import base.Decoder; + +/** + * ExtractPluginA v1.0 - 0016//0017/475x.bin language table extractor + * + * @author Codestation + */ +public class ExtractPluginA extends Decoder { + + @Override + public void extract(String filename) { + int tables_count; + byte[] paddingData; + int[] table_offset; + int offset; + try { + RandomAccessFile file = new RandomAccessFile(filename, "r"); + // reading of number of main tables + tables_count = readInt(file); + // skipping 4 bytes of unknown data + file.skipBytes(4); + table_offset = new int[tables_count]; + // read the location of each table + for (int i = 0; i < tables_count; i++) { + table_offset[i] = readInt(file); + } + String directory = filename.split("\\.")[0]; + new File(directory).mkdir(); + // create the list of string tables used in the rebuild + PrintStream filelist = new PrintStream(new FileOutputStream(new File(directory + "/filelist.txt")), true, "UTF-8"); + // save the name and size of the file + filelist.println(filename + " " + file.length()); + for (int j = 0; j < tables_count; j++) { + if(table_offset[j] == -1) { + //System.out.println("Creating " + directory + "/string_table_" + j + ".txt (empty)"); + System.out.println("Can't create " + directory + "/string_table_" + j + ".txt (null table), skipping."); + File f = new File(directory + "/string_table_" + j + ".txt"); + f.delete(); + //f.createNewFile(); + filelist.println("string_table_" + j + ".txt"); + continue; + } + file.seek(table_offset[j]); + System.out.println("Creating " + directory + "/string_table_" + j + ".txt"); + PrintStream stringout = new PrintStream(new FileOutputStream(new File(directory + "/string_table_" + j + ".txt")), true, "UTF-8"); + filelist.println("string_table_" + j + ".txt"); + int offsetCounter = 0; + // just skip the offset section (not needed) + while (true) { + offset = readInt(file); + if (offset == -1) { + break; + } + offsetCounter++; + } + int stringCounter = 0; + while (stringCounter < offsetCounter) { + String str = readString(file); + if (str.length() == 1 && str.charAt(0) == 0) { + // some offsets points to empty strings, so i put this + // string to make + // sure that it will created at the moment of repack + stringout.println(""); + } else { + str = str.substring(0, str.length() - 1); + // need one string per line, so better replace the + // newlines + stringout.println(str.replaceAll("\n", "")); + } + stringCounter++; + } + // skip the end-byte mark + file.skipBytes(1); + stringout.close(); + } + file.seek(file.getFilePointer() - 1); + // calculate the size of the ending padding data and make a file of + // it + int size = (int) (file.length() - file.getFilePointer()); + paddingData = new byte[size]; + file.read(paddingData, 0, size); + System.out.println("Creating " + directory + "/enddata.bin"); + RandomAccessFile end = new RandomAccessFile(directory + "/enddata.bin", "rw"); + filelist.println("enddata.bin"); + end.write(paddingData, 0, size); + end.setLength(end.getFilePointer()); + end.close(); + file.close(); + filelist.close(); + System.out.println("Finished!"); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/dec/ExtractPluginB.java b/src/dec/ExtractPluginB.java new file mode 100644 index 0000000..fbcaceb --- /dev/null +++ b/src/dec/ExtractPluginB.java @@ -0,0 +1,147 @@ +/* MHP2GDEC v1.0 - MHP2G 53xx.bin language table extractor + Copyright (C) 2008 Codestation + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +package dec; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.RandomAccessFile; +import java.util.Vector; + +import base.Decoder; + +/** + * ExtractPluginB v1.0 - 53xx.bin language table extractor + * + * @author Codestation + */ +public class ExtractPluginB extends Decoder { + + private int mhp3_skip_bytes; + public ExtractPluginB(boolean newdec) { + mhp3_skip_bytes = newdec ? 4 : 0; + } + + @Override + public void extract(String filename) { + byte[] unknownData; + Vector table_offset; + try { + RandomAccessFile file = new RandomAccessFile(filename, "r"); + table_offset = new Vector(); + int pointer; + while (true) { + pointer = readInt(file); + if (pointer == 0) { + break; + } + table_offset.add(pointer); + } + String directory = filename.split("\\.")[0]; + new File(directory).mkdir(); + PrintStream filelist = new PrintStream(new FileOutputStream(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)); + System.out.println("Creating " + directory + "/string_table_" + j + ".txt"); + PrintStream stringout = new PrintStream(new FileOutputStream(new File(directory + "/string_table_" + j + ".txt")), true, "UTF-8"); + filelist.println("string_table_" + j + ".txt"); + // int unknown0 = readInt(file); + // int payment = readInt(file); + // int reward = readInt(file); + // int decrease = readInt(file); + // int unknown_fixed = readInt(file); + file.skipBytes(20 + mhp3_skip_bytes); + int offset_table_pointer = readInt(file); + // int unknown1 = readInt(file); + // int unknown2 = readInt(file); + // int unknown3 = readInt(file); + // int unknown4 = readInt(file); + // int unknown5 = readInt(file); + // int unknown6 = readInt(file); + // int unknown7 = readInt(file); + file.seek(offset_table_pointer); + int string_table_pointers = readInt(file); + for (long i = string_table_pointers; i < offset_table_pointer; i += 4) { + file.seek(i); + int current_string = readInt(file); + file.seek(current_string); + String str = readString(file); + if (str.length() == 1 && str.charAt(0) == 0) { + // some offsets points to empty strings, so i put this + // string to make + // sure that it will created at the moment of re-pack + stringout.println(""); + } else { + str = str.substring(0, str.length() - 1); + // need one string per line, so better replace the + // newlines + stringout.println(str.replaceAll("\n", "")); + } + } + stringout.close(); + file.seek(offset_table_pointer + 7 * 4); + } + // 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 + "/" + filename + " (needed for rebuild)"); + copyfile(filename, directory + "/" + filename); + System.out.println("Finished!"); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void copyfile(String srFile, String dtFile) { + try { + File f1 = new File(srFile); + File f2 = new File(dtFile); + InputStream in = new FileInputStream(f1); + OutputStream out = new FileOutputStream(f2); + + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + in.close(); + out.close(); + } catch (FileNotFoundException ex) { + System.out.println(ex.getMessage() + " in the specified directory."); + System.exit(0); + } catch (IOException e) { + System.out.println(e.getMessage()); + } + } +} diff --git a/src/dec/ExtractPluginC.java b/src/dec/ExtractPluginC.java new file mode 100644 index 0000000..ecf3317 --- /dev/null +++ b/src/dec/ExtractPluginC.java @@ -0,0 +1,117 @@ +/* MHP2GDEC v1.0 - MHP2G 537x.bin language table extractor + Copyright (C) 2008 Codestation + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +package dec; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.io.RandomAccessFile; +import java.util.Vector; + +import base.Decoder; + +/** + * MHP2GDEC v1.0 - 537x.bin language table extractor + * + * @author Codestation + */ +public class ExtractPluginC extends Decoder { + + @Override + public void extract(String filename) { + Vector offset_tables = new Vector(); + Vector unknown_values = new Vector(); + try { + RandomAccessFile file = new RandomAccessFile(filename, "r"); + while (true) { + int unknown = readInt(file); + int offset = readInt(file); + if (unknown == -1 && offset == -1) { + break; + } + unknown_values.add(unknown); + offset_tables.add(offset); + + } + String directory = filename.split("\\.")[0]; + new File(directory).mkdir(); + // create the list of string tables used in the rebuild + PrintStream filelist = new PrintStream(new FileOutputStream(new File(directory + "/filelist.txt")), true, "UTF-8"); + // save the name and size of the file + filelist.println(filename + " " + file.length()); + int string_table_end = 0; + ; + for (int j = 0; j < offset_tables.size(); j++) { + file.seek(offset_tables.get(j)); + System.out.println("Creating " + directory + "/string_table_" + j + ".txt"); + PrintStream stringout = new PrintStream(new FileOutputStream(new File(directory + "/string_table_" + j + ".txt")), true, "UTF-8"); + filelist.println(unknown_values.get(j) + ",string_table_" + j + ".txt"); + int offset_table_start = (int) file.getFilePointer(); + int string_table_start = 0; + boolean first = false; + while (true) { + int unknown = readInt(file); + int offset = readInt(file); + if (!first) { + first = true; + string_table_start = offset + offset_table_start; + } + int actual_offset = (int) file.getFilePointer(); + file.seek(offset + offset_table_start); + String str = readString(file); + string_table_end = (int) file.getFilePointer(); + file.seek(actual_offset); + if (str.length() == 1 && str.charAt(0) == 0) { + // some offsets points to empty strings, so i put this + // string to make + // sure that it will created at the moment of re-pack + stringout.println(unknown + ","); + } else { + str = str.substring(0, str.length() - 1); + // need one string per line, so better replace the + // newlines + stringout.println(unknown + "," + + str.replaceAll("\n", "")); + } + if (file.getFilePointer() >= string_table_start) { + break; + } + } + stringout.close(); + } + file.seek(string_table_end); + // calculate the size of the ending unknown data and make a file of + // it + int size = (int) (file.length() - file.getFilePointer()); + byte[] unknownData = new byte[size]; + file.read(unknownData, 0, size); + file.close(); + System.out.println("Creating " + directory + "/enddata.bin"); + RandomAccessFile end = new RandomAccessFile(directory + "/enddata.bin", "rw"); + filelist.println("enddata.bin"); + filelist.close(); + end.write(unknownData, 0, size); + end.setLength(end.getFilePointer()); + end.close(); + System.out.println("Finished!"); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/enc/RebuildPluginA.java b/src/enc/RebuildPluginA.java new file mode 100644 index 0000000..d4892c2 --- /dev/null +++ b/src/enc/RebuildPluginA.java @@ -0,0 +1,194 @@ +/* MHP2GENC v1.0 - MHP 0016/475x/0017.bin language table rebuilder + Copyright (C) 2008-2010 codestation + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +package enc; + +import java.io.BufferedReader; +import java.io.EOFException; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.Vector; + +import base.Encoder; + +/** + * RebuildPluginA v2.0 - 0016/475x.bin language table rebuilder + * + * @author Codestation + */ +public class RebuildPluginA extends Encoder { + + @Override + public void compile(String filepath) { + try { + BufferedReader files = new BufferedReader(new FileReader(filepath + "/filelist.txt")); + String file = files.readLine(); + // retrieve the filename and size + String filename = file.split(" ")[0]; + long size = Integer.parseInt(file.split(" ")[1]); + // now make a list with the string tables files + Vector filenames = new Vector(); + while ((file = files.readLine()) != null) { + filenames.add(file); + } + files.close(); + RandomAccessFile out = new RandomAccessFile(filename + ".out", "rw"); + out.setLength(0); + // write the first value (number of tables) + writeInt(out, filenames.size() - 1); + int offset = 0; + offset += 8; + // write the second value (unknown...or maybe offset to tables + // offsets?) + writeInt(out, offset); + offset += (filenames.size() - 1) * 4; + // write the first table offset (fixed value) + writeInt(out, offset); + long table_offset = out.getFilePointer(); + // first write empty offset values (we don't know the values...yet) + for (int i = 1; i < filenames.size() - 1; i++) { + writeInt(out, 0); + } + for (int i = 0; i < filenames.size() - 1; i++) { + // now start to create each offset table / string table + try { + createStringTable(filepath, filenames.get(i), out); + // write the end-table mark + out.writeByte(0); + if (i < filenames.size() - 2) { + long current = out.getFilePointer(); + out.seek(table_offset); + // now we know the value of the next table, so write above + // in the main offset table + writeInt(out, (int) current); + out.seek(current); + table_offset += 4; + } + }catch(FileNotFoundException e) { + // New in MHP3: null tables + System.out.println(filenames.get(i) + " not found, assuming null table pointer."); + if (i < filenames.size() - 2) { + long current = out.getFilePointer(); + out.seek(table_offset - 4); + int last_offset = readInt(out); + out.seek(table_offset - 4); + writeInt(out, -1); + out.seek(table_offset); + writeInt(out, last_offset); + out.seek(current); + table_offset += 4; + } + } + } + // we need to know the size of the enddata, so open it now + System.out.println("Reading " + filenames.lastElement()); + RandomAccessFile enddata = new RandomAccessFile(filepath + "/" + filenames.lastElement(), "rw"); + + long enddataSize = enddata.length(); + + // some checks to make sure that the file size of xxxx.bin is + // correct + if (out.getFilePointer() > size - enddataSize) { + System.out.println("File too big (by " + (out.getFilePointer() - (size - enddataSize))+ " bytes), please reduce some strings :("); + } else if (out.getFilePointer() < size - enddataSize) { + System.out.println("File too small (by " + ((size - enddataSize) - out.getFilePointer())+ " bytes), filling with 0x00 (this is OK) :D"); + while (out.getFilePointer() < size - enddataSize) { + out.writeByte(0); + } + } else { + System.out.println("Perfect size of file :O"); + } + // now append the padding data at the end of file + int data; + while ((data = enddata.read()) != -1) { + out.write((byte) data); + } + enddata.close(); + out.setLength(out.getFilePointer()); + out.close(); + System.out.println("Finished!"); + } catch (FileNotFoundException e) { + System.out.println(e.toString()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * + * @param in + * filename of text file + * @param out + * file to write the table + * @throws FileNotFoundException + * if the file is not found + * @throws IOException + * if any error occur while reading/writing + */ + public void createStringTable(String directory, String in, RandomAccessFile out) throws FileNotFoundException, IOException { + System.out.println("Reading " + directory + "/" + in); + RandomAccessFile file = new RandomAccessFile(directory + "/" + in, "r"); + if(file.length() == 0) { + // new in MHP3: empty tables + writeInt(out, -1); + file.close(); + return; + } + checkUnicodeBOM(file); // thanks notepad :/ (who the hell uses notepad?) + Vector stringTable = new Vector(); + try { + while (true) { + // read all strings of file + String str = readString(file); + if (str == null) { + break; + } + // remove the labels and put the original data + str = str.replaceAll("", "\n"); + str = str.replaceAll("", "\0"); + stringTable.add(str); + } + } catch (EOFException e) { + file.close(); + } + int offset = stringTable.size() * 4; + offset += 4; + // now calculate the offsets using the length in bytes of the strings + for (int i = 0; i < stringTable.size(); i++) { + writeInt(out, offset); + if (stringTable.elementAt(i).getBytes("UTF-8").length == 1 && stringTable.elementAt(i).charAt(0) == '\0') { + offset++; + } else { + offset += stringTable.elementAt(i).getBytes("UTF-8").length + 1; + } + } + // end of offset table mark + writeInt(out, -1); + // now write the zero terminated string in the file + for (int i = 0; i < stringTable.size(); i++) { + String str = stringTable.elementAt(i); + if (str.equals("\0")) { + out.writeByte(0); + } else { + out.write(str.getBytes("UTF-8")); + out.writeByte(0); + } + } + } +} diff --git a/src/enc/RebuildPluginB.java b/src/enc/RebuildPluginB.java new file mode 100644 index 0000000..6603b75 --- /dev/null +++ b/src/enc/RebuildPluginB.java @@ -0,0 +1,176 @@ +/* MHP2GENC v1.0 - MHP2G 53xx.bin language table rebuilder + Copyright (C) 2008 Codestation + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +package enc; + +import java.io.BufferedReader; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.io.UnsupportedEncodingException; +import java.util.Vector; + +import base.Encoder; + +/** + * RebuildPluginB v1.0 - 53xx.bin language table rebuilder + * + * @author codestation + */ +public class RebuildPluginB extends Encoder { + + private int encoder = 0; + public RebuildPluginB(int type) { + encoder = type; + } + + @Override + public void compile(String filepath) { + try { + BufferedReader files = new BufferedReader(new FileReader(filepath + "/filelist.txt")); + String file = files.readLine(); + // retrieve the filename and size + String filename = file.split(" ")[0]; + // long size = Integer.parseInt(file.split(" ")[1]); + // now make a list with the string tables files + Vector filenames = new Vector(); + while ((file = files.readLine()) != null) { + filenames.add(file); + } + files.close(); + copyfile(filepath + "/" + filename, filename + ".out"); + RandomAccessFile out = new RandomAccessFile(filename + ".out", "rw"); + Vector table_offset = new Vector(); + 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)); + } + out.close(); + System.out.println("Finished!"); + } catch (FileNotFoundException e) { + System.out.println(e.toString()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void patchStringTable(String directory, String in, RandomAccessFile out, int starting_offset) throws FileNotFoundException, IOException { + System.out.println("Reading " + directory + "/" + in); + RandomAccessFile file = new RandomAccessFile(directory + "/" + in, "r"); + checkUnicodeBOM(file); // thanks notepad :/ (die notepad, die) + Vector stringTable = new Vector(); + try { + while (true) { + // read all strings of file + String str = readString(file); + if (str == null) { + break; + } + // remove the labels and put the original data + str = str.replaceAll("", "\n"); + str = str.replaceAll("", "\0"); + stringTable.add(str); + } + } catch (EOFException e) { + } + file.close(); + out.seek(starting_offset); + out.skipBytes(20 + encoder); + int offset_table_pointer = readInt(out); + int string_start = (int) out.getFilePointer() + 28; + out.seek(offset_table_pointer); + int string_table_pointers = readInt(out); + int diff = string_table_pointers - string_start - calculateTotalSize(stringTable); + if (diff < 0) { + System.err.println(in + " is too big, please remove at least " + -diff + " bytes. Skipped"); + return; + } + out.seek(string_table_pointers); + int starting_string = readInt(out); + out.seek(starting_string); + long orig_table_pointer = string_table_pointers; + for (String str : stringTable) { + out.write(str.getBytes("UTF-8")); + out.writeByte(0); + out.writeByte(0); + while (out.getFilePointer() % 4 != 0) { + out.writeByte(0); + } + int tmp = (int) out.getFilePointer(); + out.seek(string_table_pointers); + writeInt(out, starting_string); + string_table_pointers += 4; + starting_string = tmp; + out.seek(tmp); + } + long current_offset = out.getFilePointer(); + while (current_offset < orig_table_pointer) { + out.writeByte(0); + current_offset++; + } + } + + private int calculateTotalSize(Vector st) throws UnsupportedEncodingException { + int total = 0; + for (String str : st) { + int len = str.getBytes("UTF-8").length; + + if (len == 1 && str.charAt(0) == 0) { + total++; + } else { + total += len + 1; + } + } + return total; + } + + private void copyfile(String srFile, String dtFile) { + try { + File f1 = new File(srFile); + File f2 = new File(dtFile); + InputStream in = new FileInputStream(f1); + OutputStream out = new FileOutputStream(f2); + + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + in.close(); + out.close(); + } catch (FileNotFoundException ex) { + System.out.println(ex.getMessage() + " in the specified directory."); + System.exit(0); + } catch (IOException e) { + System.out.println(e.getMessage()); + } + } +} diff --git a/src/enc/RebuildPluginC.java b/src/enc/RebuildPluginC.java new file mode 100644 index 0000000..10c360d --- /dev/null +++ b/src/enc/RebuildPluginC.java @@ -0,0 +1,176 @@ +/* MHP2GENC v1.0 - MHP2G 537x.bin language table rebuilder + Copyright (C) 2008 Codestation + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +package enc; + +import java.io.BufferedReader; +import java.io.EOFException; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.Vector; + +import base.Encoder; + +/** + * RebuildPluginC v1.0 - 537x.bin language table rebuilder + * + * @author Codestation + */ +public class RebuildPluginC extends Encoder { + + @Override + public void compile(String filepath) { + try { + BufferedReader files = new BufferedReader(new FileReader(filepath + "/filelist.txt")); + String string_file = files.readLine(); + // retrieve the filename and size + String filename = string_file.split(" ")[0]; + long size = Integer.parseInt(string_file.split(" ")[1]); + // now make a list with the string tables files + Vector filenames = new Vector(); + Vector unknown = new Vector(); + while ((string_file = files.readLine()) != null) { + if (string_file.equals("enddata.bin")) { + filenames.add(string_file); + } else { + filenames.add(string_file.split(",")[1]); + unknown.add(Integer.parseInt(string_file.split(",")[0])); + } + } + files.close(); + RandomAccessFile out = new RandomAccessFile(filename + ".out", "rw"); + writeInt(out, unknown.get(0)); + writeInt(out, (unknown.size() + 1) * 8); + for (int i = 1; i < unknown.size(); i++) { + writeInt(out, unknown.get(i)); + writeInt(out, 0); + } + writeInt(out, -1); + writeInt(out, -1); + int table_offset = 12; + for (int i = 0; i < filenames.size() - 1; i++) { + // now start to create each offset table / string table + createStringTable(filepath, filenames.get(i), out); + // write the end-table mark + out.writeByte(0); + if (i < filenames.size() - 2) { + long current = out.getFilePointer(); + out.seek(table_offset); + // now we know the value of the next table, so write above + // in the main + // offset table + writeInt(out, (int) current); + out.seek(current); + table_offset += 8; + out.seek(current); + } + } + out.writeByte(0); + // we need to know the size of the enddata, so open it now + System.out.println("Reading " + filenames.lastElement()); + RandomAccessFile enddata = new RandomAccessFile(filepath + "/" + filenames.lastElement(), "rw"); + + long enddataSize = enddata.length(); + + // some checks to make sure that the file size of xxxx.bin is + // correct + if (out.getFilePointer() > size - enddataSize) { + System.out.println("File too big (by " + (out.getFilePointer() - (size - enddataSize))+ " bytes), please reduce some strings :("); + } else if (out.getFilePointer() < size - enddataSize) { + System.out.println("File too small (by " + ((size - enddataSize) - out.getFilePointer())+ " bytes), filling with 0x00 (this is OK) :D"); + while (out.getFilePointer() < size - enddataSize) { + out.writeByte(0); + } + } else { + System.out.println("Perfect size of file :O"); + } + // now append the unknown data at the end of file + int data; + while ((data = enddata.read()) != -1) { + out.write((byte) data); + } + enddata.close(); + out.setLength(out.getFilePointer()); + out.close(); + System.out.println("Finished!"); + } catch (FileNotFoundException e) { + System.out.println(e.toString()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * + * @param in + * filename of text file + * @param out + * file to write the table + * @throws FileNotFoundException + * if the file is not found + * @throws IOException + * if any error occur while reading/writing + */ + private void createStringTable(String directory, String in, RandomAccessFile out) throws FileNotFoundException, IOException { + System.out.println("Reading " + directory + "/" + in); + RandomAccessFile file = new RandomAccessFile(directory + "/" + in, "r"); + checkUnicodeBOM(file); // thanks notepad :/ (*sigh*) + Vector stringTable = new Vector(); + Vector unknownTable = new Vector(); + try { + while (true) { + // read all strings of file + String str = readString(file); + if (str == null) { + break; + } + // remove the labels and put the original data + unknownTable.add(Integer.parseInt(str.split(",")[0])); + str = str.substring(str.indexOf(",") + 1); + str = str.replaceAll("", "\n"); + str = str.replaceAll("", "\0"); + stringTable.add(str); + } + } catch (EOFException e) { + } + file.close(); + int offset = stringTable.size() * 8; + // now calculate the offsets using the length in bytes of the strings + for (int i = 0; i < stringTable.size(); i++) { + writeInt(out, unknownTable.get(i)); + writeInt(out, offset); + if (stringTable.elementAt(i).getBytes("UTF-8").length == 1 + && stringTable.elementAt(i).charAt(0) == '\0') { + offset++; + } else { + offset += stringTable.elementAt(i).getBytes("UTF-8").length + 1; + } + } + // now write the zero terminated string in the file + for (int i = 0; i < stringTable.size(); i++) { + String str = stringTable.elementAt(i); + if (str.equals("\0")) { + out.writeByte(0); + } else { + out.write(str.getBytes("UTF-8")); + out.writeByte(0); + } + } + } +}