public static void bspatch(InputStream oldFileIn, OutputStream newFileOut, InputStream diffFileIn) throws IOException {
    int oldPos, newPos;

    byte[] diffData = Utils.readBytes(diffFileIn);

    DataInputStream diffIn = new DataInputStream(new ByteArrayInputStream(diffData));

    // ctrlBlockLen after gzip compression at heater offset 0 (length 8 bytes)
    long ctrlBlockLen = diffIn.readLong();

    // diffBlockLen after gzip compression at header offset 8 (length 8 bytes)
    long diffBlockLen = diffIn.readLong();

    // size of new file at header offset 16 (length 8 bytes)
    int newSize = (int)diffIn.readLong();

    InputStream in;
    in = new ByteArrayInputStream(diffData);
    skip(in, ctrlBlockLen + 24);
    GZIPInputStream diffBlockIn = new GZIPInputStream(in);

    in = new ByteArrayInputStream(diffData);
    skip(in, diffBlockLen + ctrlBlockLen + 24);
    GZIPInputStream extraBlockIn = new GZIPInputStream(in);

    byte[] oldBuf = oldFileIn.readAllBytes();
    int oldSize = oldBuf.length;

    byte[] newBuf = new byte[BLOCK_SIZE];

    oldPos = 0;
    newPos = 0;

    while (newPos < newSize) {
      int bytesToReadFromDiffAndOld = diffIn.readInt();
      int bytesToReadFromExtraBlockIn = diffIn.readInt();
      int bytesToSkipFromOld = diffIn.readInt();

      if (newPos + bytesToReadFromDiffAndOld > newSize) {
        throw new IOException("Corrupted patch");
      }

      int nBytes = 0;
      while (nBytes < bytesToReadFromDiffAndOld) {
        int nBytesFromDiff = diffBlockIn.read(newBuf, 0, Math.min(newBuf.length, bytesToReadFromDiffAndOld - nBytes));
        if (nBytesFromDiff < 0) {
          throw new IOException("error reading from diffBlockIn");
        }

        int nBytesFromOld = Math.min(nBytesFromDiff, oldSize - oldPos);
        for (int i = 0; i < nBytesFromOld; ++i) {
          newBuf[i] += oldBuf[oldPos + i];
        }

        nBytes += nBytesFromDiff;
        newPos += nBytesFromDiff;
        oldPos += nBytesFromDiff;
        Utils.writeBytes(newBuf, nBytesFromDiff, newFileOut);
      }

      if (bytesToReadFromExtraBlockIn > 0) {
        if (newPos + bytesToReadFromExtraBlockIn > newSize) {
          throw new IOException("Corrupted patch");
        }

        nBytes = 0;
        while (nBytes < bytesToReadFromExtraBlockIn) {
          int nBytesFromExtraBlockIn = extraBlockIn.read(newBuf, 0, Math.min(newBuf.length, bytesToReadFromExtraBlockIn - nBytes));
          if (nBytesFromExtraBlockIn < 0) {
            throw new IOException("error reading from extraBlockIn");
          }

          nBytes += nBytesFromExtraBlockIn;
          newPos += nBytesFromExtraBlockIn;
          Utils.writeBytes(newBuf, nBytesFromExtraBlockIn, newFileOut);
        }
      }

      oldPos += bytesToSkipFromOld;
    }

    // TODO: Check if at end of ctrlIn
    // TODO: Check if at the end of diffIn
    // TODO: Check if at the end of extraIn

    diffBlockIn.close();
    extraBlockIn.close();
    diffIn.close();
  }
public static void bspatch(InputStream oldFileIn, OutputStream newFileOut, InputStream diffFileIn)