class UPS extends Patcher {

    @Override
    public void apply(boolean ignoreChecksum) throws PatchException, IOException {

        if (patchFile.length() < 18) {
            throw new PatchException(context.getString(R.string.notify_error_patch_corrupted));
        }

        BufferedInputStream patchStream = null;
        BufferedInputStream romStream = null;
        BufferedOutputStream outputStream = null;
        UpsCrc upsCrc;
        try {
            if (!checkMagic(patchFile))
                throw new PatchException(context.getString(R.string.notify_error_not_ups_patch));

            upsCrc = readUpsCrc(context, patchFile);
            if (upsCrc.getPatchFileCRC() != upsCrc.getRealPatchCRC())
                throw new PatchException(context.getString(R.string.notify_error_patch_corrupted));

            patchStream = new BufferedInputStream(new FileInputStream(patchFile));
            long patchPos = 0;
            // skip magic
            for (int i = 0; i < 4; i++) {
                patchStream.read();
            }
            patchPos += 4;

            // decode rom and output size
            Pair p;
            p = decode(patchStream);
            long xSize = p.getValue();
            patchPos += p.getSize();
            p = decode(patchStream);
            long ySize = p.getValue();
            patchPos += p.getSize();

            long realRomCrc = FileUtils.checksumCRC32(romFile);

            if (romFile.length() == xSize && realRomCrc == upsCrc.getInputFileCRC()) {
                // xSize, ySize, inCRC, outCRC not change
            } else if (romFile.length() == ySize && realRomCrc == upsCrc.getOutputFileCRC()) {
                // swap(xSize, ySize) and swap(inCRC, outCRC)
                long tmp = xSize;
                xSize = ySize;
                ySize = tmp;
                upsCrc.swapInOut();
            } else {
                if (!ignoreChecksum) {
                    throw new IOException(context.getString(R.string.notify_error_rom_not_compatible_with_patch));
                }
            }

            romStream = new BufferedInputStream(new FileInputStream(romFile));
            outputStream = new BufferedOutputStream(new FileOutputStream(outputFile));
            long outPos = 0;

            int x, y;
            long offset = 0;
            while (patchPos < patchFile.length() - 12) {
                p = decode(patchStream);
                offset += p.getValue();
                patchPos += p.getSize();
                if (offset > ySize) continue;
                Utils.INSTANCE.copy(romStream, outputStream, offset - outPos);
                outPos += offset - outPos;
                for (long i = offset; i < ySize; i++) {
                    x = patchStream.read();
                    patchPos++;
                    offset++;
                    if (x == 0x00) break; // chunk terminating byte - 0x00
                    y = i < xSize ? romStream.read() : 0x00;
                    outputStream.write(x ^ y);
                    outPos++;
                }
            }
            // write rom tail and trim
            Utils.INSTANCE.copy(romStream, outputStream, ySize - outPos);

        } finally {
            IOUtils.closeQuietly(patchStream);
            IOUtils.closeQuietly(romStream);
            IOUtils.closeQuietly(outputStream);
        }

        if (!ignoreChecksum) {
            long realOutCrc = FileUtils.checksumCRC32(outputFile);
            if (realOutCrc != upsCrc.getOutputFileCRC())
                throw new PatchException(context.getString(R.string.notify_error_wrong_checksum_after_patching));
        }
    }

    public  UPS(Context context, File patch, File rom, File output);

}