package mwt.wow.mpq; import java.io.IOException; import java.io.InputStream; class MpqInputStream extends InputStream { private MpqFile file; private Reader sectorReader; private long length; private int[] sectorTable; private int sectorSize; private int readerSector; private int readerOffset; MpqInputStream(MpqFile file) throws IOException { this.file = file; FileReader reader = new FileReader(file.mpqArchive.randomAccessFile, file.mpqArchive.archiveOffset + file.blockTableEntry.getBlockOffset()); length = file.blockTableEntry.getFileSize() & 0xffffffffl; if (file.blockTableEntry.isSingleUnit()) { sectorSize = file.blockTableEntry.getFileSize(); } else { sectorSize = 512 * (1 << file.mpqArchive.sectorSizeShift); } if ((file.blockTableEntry.isCompressed() || file.blockTableEntry .isImploded()) && !file.blockTableEntry.isSingleUnit()) { sectorTable = new int[(file.blockTableEntry.getFileSize() + sectorSize - 1) / sectorSize + 1]; for (int i = 0; i < sectorTable.length; i++) { sectorTable[i] = reader.readInt32(); } } readerSector = -1; readerOffset = 0; } @Override public int read() throws IOException { if (readerOffset >= length) { return -1; } if (readerOffset / sectorSize == readerSector + 1) { prepareSector(); } readerOffset++; return sectorReader.readInt8(); } @Override public int read(byte[] b, int off, int len) throws IOException { if (b == null) { throw new NullPointerException(); } else if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; } if (readerOffset >= length) { return -1; } if (len > length - readerOffset) { len = (int) (length - readerOffset); } int bytesRead = 0; while (bytesRead < len && readerOffset < length) { if (readerOffset / sectorSize == readerSector + 1) { prepareSector(); } int bytesAvailable = sectorSize - (readerOffset % sectorSize); int remaining = len - bytesRead; int toRead = bytesAvailable < remaining ? bytesAvailable : remaining; toRead = sectorReader.readBlock(b, off + bytesRead, toRead); if (toRead == -1) { return bytesRead == 0 ? -1 : bytesRead; } readerOffset += toRead; bytesRead += toRead; } return bytesRead; } private void prepareSector() throws IOException { readerSector++; if (file.blockTableEntry.isCompressed() || file.blockTableEntry.isImploded()) { int compressedSectorSize; if (file.blockTableEntry.isSingleUnit()) { compressedSectorSize = file.blockTableEntry.getBlockSize(); } else { compressedSectorSize = sectorTable[readerSector + 1] - sectorTable[readerSector]; } long curSectorSize = length - readerOffset; if (!file.blockTableEntry.isSingleUnit() && curSectorSize > sectorSize) { curSectorSize = sectorSize; } if (!file.blockTableEntry.isSingleUnit()) { sectorReader = new FileReader(file.mpqArchive.randomAccessFile, file.mpqArchive.archiveOffset + file.blockTableEntry.getBlockOffset() + sectorTable[readerSector]); } else { sectorReader = new FileReader(file.mpqArchive.randomAccessFile, file.mpqArchive.archiveOffset + file.blockTableEntry.getBlockOffset() + readerSector * sectorSize); } if (file.blockTableEntry.isEncrypted()) { throw new IOException("todo encryption"); } if (file.blockTableEntry.isCompressed()) { if (compressedSectorSize < curSectorSize) { int compressionFlags = sectorReader.readInt8(); if ((compressionFlags & 0x10) != 0) { sectorReader = new BZip2Reader(sectorReader, compressedSectorSize - 1); } if ((compressionFlags & 0x08) != 0) { throw new IOException("todo imploded"); } if ((compressionFlags & 0x02) != 0) { sectorReader = new DeflateReader(sectorReader, compressedSectorSize - 1); } if ((compressionFlags & 0x01) != 0) { throw new IOException("todo huffman"); } if ((compressionFlags & 0x80) != 0) { throw new IOException("ima adpcm stereo"); } if ((compressionFlags & 0x40) != 0) { throw new IOException("ima adpcm mono"); } if ((compressionFlags & 0x24) != 0) { throw new IOException("unsupported compression " + String.format("%02x", compressionFlags)); } } } else { throw new IOException(); } } else { sectorReader = new FileReader(file.mpqArchive.randomAccessFile, file.mpqArchive.archiveOffset + file.blockTableEntry.getBlockOffset() + readerSector * sectorSize); } } @Override public void close() throws IOException { super.close(); if (sectorReader != null) { sectorReader.close(); } file = null; sectorReader = null; } }