Package net.bnubot.util.mpq

Source Code of net.bnubot.util.mpq.MPQFile

/**
* This file is distributed under the GPL
* $Id: MPQFile.java 1896 2014-02-11 10:57:38Z scotta $
*/
package net.bnubot.util.mpq;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
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.InputStreamReader;

import net.bnubot.util.BNetInputStream;
import net.bnubot.util.BNetOutputStream;

/**
* http://www.zezula.net/en/mpq/techinfo.html
* @author scotta
*/
public class MPQFile implements MPQConstants {
  /**
   * Indicates a sub-file
   */
  private static final String FILE_SEPERATOR = "\\";

  public static void main(String[] args) throws IOException {
//    test(new MPQFile(new File("bncache.dat")));
//    test(new MPQFile(new File("War3ROC_122a_123a_English.exe")));
    test(new MPQFile(new File("C:\\Users\\Scott\\Desktop\\SystemCheck_enUS.exe")));
  }

  private static void test(MPQFile mpq) {
    try {
      mpq.suggestFileNames(new FileInputStream(new File("LIST.TXT")));
    } catch(Exception e) {}
    try {
      mpq.readListFile();
    } catch(Exception e) {}
    for(int i = 0; i < mpq.file_names.length; i++) {
      int flags = mpq.block_table[(i<<2)+3];
      if((flags & MPQ_FILE_EXISTS) == 0)
        continue; // File was deleted

      try {
        MPQFile mpq2 = mpq.readMPQ(i);
        test(mpq2);
      } catch(Exception e) {
        if(mpq.file_names[i] == null)
          System.err.println(mpq.getFileName(i));
      }
    }
  }

  private final String fileName;
  private BNetInputStream is;
  private final int[] hash_table;
  private final int[] block_table;
  private final String[] file_names;

  public MPQFile(File file) throws IOException {
    this(new FileInputStream(file), file.getName());
  }

  private MPQFile(InputStream is0, String fileName) throws IOException {
    if(is0 == null)
      throw new NullPointerException();

    this.fileName = fileName;

    // Make sure mark support is available
    if(!is0.markSupported())
      is0 = new BufferedInputStream(is0);
    is = new BNetInputStream(is0);
    is.mark(Integer.MAX_VALUE);

    // Search for the MPQ header
    final int offset_mpq = findHeader();

    // Jump to the beginning of the archive
    is.reset();
    is.skip(offset_mpq);

    // 32-byte header
    is.skip(4); //int file_format = is.readDWord();
    is.skip(4); //int header_size = is.readDWord();
    int archive_size = is.readDWord();
    is.skip(2); //short format_version = is.readWord();
    is.skip(2); //short block_size = is.readWord();
    final int offset_htbl = is.readDWord();
    final int offset_btbl = is.readDWord();
    final int count_htbl = is.readDWord() << 2;
    final int num_files = is.readDWord();
    final int count_btbl = num_files << 2;

    // Jump to the beginning of the archive
    is.reset();
    is.skip(offset_mpq);

    // Read the entire archive
    byte[] data = new byte[archive_size];
    is.readFully(data);

    // Rebuild the InputStream with just the archive data
    is = new BNetInputStream(new ByteArrayInputStream(data));
    is.mark(archive_size);

    // Jump to the hash table
    is.reset();
    is.skip(offset_htbl);

    // Read the hash table
    hash_table = new int[count_htbl];
    for(int i = 0; i < count_htbl; i++)
      hash_table[i] = is.readDWord();

    // Decrypt the hash table
    MPQUtils.decrypt(hash_table,MPQUtils.crc(HASH_TABLE,3));

    // Jump to the block table
    is.reset();
    is.skip(offset_btbl);

    // Read the block table
    block_table = new int[count_btbl];
    for(int i = 0; i < count_btbl; i++)
      block_table[i] = is.readDWord();

    // Decrypt the block table
    MPQUtils.decrypt(block_table,MPQUtils.crc(BLOCK_TABLE,3));

    // Try to figure out the file names from a list file
    file_names = new String[num_files];
  }

  private int findHeader() throws IOException {
    int offset_mpq = 0;
    while(true) {
      if(offset_mpq > 0x200) {
        int rem = 0x200 - (offset_mpq & 0x1FF);
        if(is.available() < rem)
          throw new IOException("Invalid MPQ archive");
        if(rem != 0) {
          is.skipBytes(rem);
          offset_mpq += rem;
        }
      }

      if(is.available() < 4)
        throw new IOException("Invalid MPQ archive");

      int file_format = is.readDWord();
      switch(file_format) {
      case ID_MPQ_SHUNT:
        throw new IllegalStateException("Not sure how to process MPQ SHUNT header");
      case ID_BN3:
      case ID_MPQ:
        int header_size = is.readDWord();
        is.skipBytes(4); //int archive_size = is.readDWord();
        short format_version = is.readWord();
        is.skipBytes(2); //short block_size = is.readWord();

        if(((format_version == 0) && (header_size == 0x20))
        || ((format_version == 1) && (header_size == 0x2C)))
          return offset_mpq;

        offset_mpq += 16;
        break;
      default:
        // Keep searching
        offset_mpq += 4;
        break;
      }
    }
  }

  public String getFileName(int fileNum) {
    if(file_names[fileNum] != null)
      return this.fileName + FILE_SEPERATOR + "[" + fileNum + "]" + file_names[fileNum];

    if(!listFileRead)
      try {
        readListFile();
        if(file_names[fileNum] != null)
          return this.fileName + FILE_SEPERATOR + "[" + fileNum + "]" + file_names[fileNum];
      } catch(IOException e) {}

    String fileName = "0000" + fileNum;
    fileName = fileName.substring(fileName.length() - 5);
    return this.fileName + FILE_SEPERATOR + "[" + fileNum + "]unknown\\unk" + fileName + ".xxx";
  }

  private boolean listFileRead = false;
  private void readListFile() throws IOException {
    suggestFileNames(readFile(LISTFILE));
    listFileRead = true;
  }

  private void suggestFileNames(InputStream is) {
    BufferedReader f = new BufferedReader(new InputStreamReader(is));
    while(true) {
      try {
        final String fn = f.readLine();
        if(fn == null)
          break;
        suggestFileName(fn);
      } catch(IOException e) {
        break;
      }
    }
  }

  private int getFileNumber(String str) {
    final int table_size = hash_table.length;

      final int nHash = MPQUtils.crc(str, 0);
      final int nHashA = MPQUtils.crc(str, 1);
      final int nHashB = MPQUtils.crc(str, 2);
    int nHashStart = nHash % table_size;
    if(nHashStart < 0)
      nHashStart += table_size;
    int nHashPos = nHashStart;

      while(true) {
          if((hash_table[nHashPos] == nHashA)
          && (hash_table[nHashPos + 1] == nHashB))
              return hash_table[nHashPos+3];

          // Increment the hash position
          nHashPos++;
          nHashPos %= table_size;
          // We went all the way around the table and got back to where we started
          if (nHashPos == nHashStart)
              return -1;
      }
  }

  private void suggestFileName(String fileName) {
    int i = getFileNumber(fileName);
    if(i != -1)
      file_names[i] = fileName;
  }

  public File writeFile(String fileName) throws IOException {
    // Read the file
    InputStream is = readFile(fileName);
    byte[] data = new byte[is.available()];
    is.read(data);

    // Write the file
    System.out.println("Writing " + fileName + ", " + data.length + " bytes");
    File f = new File(fileName.substring(fileName.lastIndexOf('\\')+1));
    FileOutputStream fos = new FileOutputStream(f);
    fos.write(data);
    fos.close();

    // All done
    return f;
  }

  public InputStream readFile(String fileName) throws IOException {
    int i = getFileNumber(fileName);
    if(i == -1)
      throw new FileNotFoundException(fileName);

    file_names[i] = fileName;
    return readFile(i);
  }

  public MPQFile readMPQ(String fileName) throws IOException {
    int i = getFileNumber(fileName);
    if(i == -1)
      throw new FileNotFoundException(fileName);

    file_names[i] = fileName;
    return readMPQ(i);
  }

  public MPQFile readMPQ(int fileNum) throws IOException {
    return new MPQFile(readFile(fileNum), getFileName(fileNum));
  }

  public InputStream readFile(final int fileNum) throws IOException {
    final int offset = block_table[fileNum<<2];
    final int size_packed = block_table[(fileNum<<2)+1];
    final int size_unpacked = block_table[(fileNum<<2)+2];
    final int flags = block_table[(fileNum<<2)+3];

    final boolean f_imploded = ((flags & MPQ_FILE_IMPLODE) != 0);
    final boolean f_compressed = ((flags & MPQ_FILE_COMPRESS) != 0);
    final boolean f_encrypted = ((flags & MPQ_FILE_ENCRYPTED) != 0);
    final boolean f_fixseed = ((flags & MPQ_FILE_FIXSEED) != 0);
    final boolean f_single_unit = ((flags & MPQ_FILE_SINGLE_UNIT) != 0);
    final boolean f_dummy_file = ((flags & MPQ_FILE_DUMMY_FILE) != 0);
    final boolean f_has_extra = ((flags & MPQ_FILE_HAS_EXTRA) != 0);
    final boolean f_exists = ((flags & MPQ_FILE_EXISTS) != 0);

    String pretty_file_name = getFileName(fileNum);

    if(f_dummy_file)
      throw new IOException(pretty_file_name + " is a dummy file");

    if(!f_exists)
      throw new FileNotFoundException(pretty_file_name + " was deleted");

    System.out.println(pretty_file_name + ": " +
        size_packed + "/" + size_unpacked + " = " +
        (int)(100f*size_packed/size_unpacked) + "%");

    int crc_file = 0;
    if(f_encrypted) {
      // If file is encrypted, calculate its crc
      String fn = file_names[fileNum];
      if(fn != null) {
        // Calculate crc_file for identified file:
        int i = fn.lastIndexOf('\\');
        if(i != -1)
          fn = fn.substring(i+1);

        // calculate crc_file (for Diablo I MPQs)
        crc_file = MPQUtils.crc(fn, 3);
        if(f_fixseed) {
          // calculate crc_file (for Starcraft MPQs)
          crc_file=(crc_file+offset)^size_unpacked;
        }

      } else {
        // calculate crc_file for not identified file:
        //crc_file=getUnknowCrc(entry);
        throw new IllegalStateException("Can't calculate CRC for unidentified files");
      }
    }

    is.reset();
    is.skip(offset);

    try (
      ByteArrayOutputStream baos = new ByteArrayOutputStream(size_unpacked);
      BNetOutputStream os = new BNetOutputStream(baos);
    ) {
      if(f_single_unit) {
        byte[] data = new byte[size_packed];
        is.readFully(data);
        if(f_encrypted)
          MPQUtils.decrypt(data, crc_file-1);

        if(data.length==size_unpacked) {
          // The block is unpacked
        } else {
          // Block is packed
          if(f_compressed) {
            // Multiple compressions are possible
            data = MPQUtils.unpack(data, size_unpacked);
          } else {
            // Just DCLib
            data = Explode.explode(data, 0, data.length, size_unpacked);
          }
        }
        os.write(data);
      } else if(f_imploded | f_compressed) {
        final int block_size = 0x1000;

        int num_blocks = ((size_unpacked-1)/block_size)+2;
        int header[] = new int[num_blocks + (f_has_extra ? 1 : 0)];
        for(int i = 0; i < header.length; i++)
          header[i] = is.readDWord();
        if(f_encrypted)
          MPQUtils.decrypt(header, crc_file-1);

        for(int i = 0; i < num_blocks-1; i++) {
          int length_read=header[i+1]-header[i];
          byte[] data = new byte[length_read];
          is.readFully(data);
          if(f_encrypted)
            MPQUtils.decrypt(data, crc_file++);

          final int out_size;
          if(i==num_blocks-2) {
            if((size_unpacked & 0xFFF) == 0) {
              // The last block could be either [0] or [block_size]
              out_size = size_unpacked-(block_size*i);
            } else {
              out_size = (size_unpacked & 0xFFF);
            }
          } else {
            out_size = block_size;
          }

          if(length_read==out_size) {
            // The block is unpacked
          } else {
            // Block is packed
            if(f_compressed) {
              // Multiple compressions are possible
              data = MPQUtils.unpack(data, out_size);
            } else {
              // Just DCLib
              data = Explode.explode(data, 0, data.length, out_size);
            }
          }
          os.write(data);
        }
      } else {
        // File is not compressed
        int block_size = f_encrypted ? 0x1000 : 0x60000;
        for(int pos=0; pos<size_packed; pos += block_size) {
          int length_read = block_size;
          if(length_read + pos > size_packed)
            length_read = size_packed % block_size;

          byte[] data = new byte[length_read];
          is.readFully(data);
          if(f_encrypted)
            MPQUtils.decrypt(data, crc_file++);
          os.write(data);
        }
      }
      return new ByteArrayInputStream(baos.toByteArray());
    }
  }

  @Override
  public String toString() {
    return fileName;
  }
}
TOP

Related Classes of net.bnubot.util.mpq.MPQFile

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.