/*
 * SDK Pullenti Unitext, version 4.33, fabruary 2026. Copyright (c) 2013-2026, Pullenti. All rights reserved. 
 * Non-Commercial Freeware and Commercial Software. 
 * This class is generated using the converter Unisharping (www.unisharping.ru) from Pullenti C# project. 
 * The latest version of the code is available on the site www.pullenti.ru
 */

package com.pullenti.unitext.internal.zip;

// This class represents a Zip archive.  You can ask for the contained
// entries, or get an input stream for a file entry.  The entry is
// automatically decompressed.
public class ZipFile implements AutoCloseable {

    public ZipFile(String _name, com.pullenti.unisharp.Stream stream) throws Exception, java.io.FileNotFoundException, java.io.IOException {
        if(_globalInstance == null) return;
        name_ = _name;
        if (stream == null) {
            m_BaseStream = new com.pullenti.unisharp.FileStream(_name, "r", "");
            m_StreamOwner = true;
        }
        else {
            m_BaseStream = stream;
            m_StreamOwner = false;
        }
        try {
            this.readEntries();
        } catch (Exception ex562) {
            this.disposeInternal(true);
            throw ex562;
        }
    }

    @Override
    public void close() {
        try {
            this.disposeInternal(true);
        } catch (Exception ex123) {
        }
    }

    public static ZipFile createFile(String fileName) throws IllegalArgumentException, java.io.FileNotFoundException, java.io.IOException, Exception {
        if (fileName == null) 
            throw new IllegalArgumentException("fileName");
        com.pullenti.unisharp.FileStream f = new com.pullenti.unisharp.FileStream(fileName, "rw", "t");
        ZipFile result = new ZipFile(null, f);
        result.name_ = fileName;
        result.m_StreamOwner = true;
        return result;
    }

    public static ZipFile createStream(com.pullenti.unisharp.Stream outStream) throws IllegalArgumentException, IllegalArgumentException, Exception, java.io.FileNotFoundException, java.io.IOException {
        if (outStream == null) 
            throw new IllegalArgumentException("outStream");
        if (!outStream.canWrite()) 
            throw new IllegalArgumentException("Stream is not writeable" + " " + "outStream");
        if (!outStream.canSeek()) 
            throw new IllegalArgumentException("Stream is not seekable" + " " + "outStream");
        ZipFile result = new ZipFile(null, outStream);
        return result;
    }

    public boolean isStreamOwner() {
        return m_StreamOwner;
    }

    public boolean setStreamOwner(boolean value) {
        m_StreamOwner = value;
        return value;
    }


    public boolean isEmbeddedArchive() {
        return offsetOfFirstEntry > 0;
    }


    public boolean isNewArchive() {
        return isNewArchive_;
    }


    public String getZipFileComment() {
        return comment_;
    }


    public String getName() {
        return name_;
    }


    public int getSize() {
        return entries_.length;
    }


    public long getCount() {
        return (long)entries_.length;
    }


    public ZipEntry getIndexerItem(int index) {
        return (ZipEntry)entries_[index].clone();
    }


    public java.util.ArrayList<ZipEntry> getZipEntries() {
        return new java.util.ArrayList<ZipEntry>(java.util.Arrays.asList(entries_));
    }


    public int findEntry(String _name, boolean ignoreCase) throws Exception {
        if (isDisposed_) 
            throw new Exception("ZipFile");
        for (int i = 0; i < entries_.length; i++) {
            if (com.pullenti.unisharp.Utils.stringsCompare(_name, entries_[i].getName(), ignoreCase) == 0) 
                return i;
        }
        return -1;
    }

    public ZipEntry getEntry(String _name) throws Exception {
        if (isDisposed_) 
            throw new Exception("ZipFile");
        int index = this.findEntry(_name, true);
        return (index >= 0 ? (ZipEntry)entries_[index].clone() : null);
    }

    public com.pullenti.unisharp.Stream getInputStream(ZipEntry entry) throws IllegalArgumentException, Exception, Exception, IndexOutOfBoundsException, java.io.IOException, java.io.EOFException {
        if (entry == null) 
            throw new IllegalArgumentException("entry");
        if (isDisposed_) 
            throw new Exception("ZipFile");
        int index = entry.getZipFileIndex();
        if (((index < 0)) || (index >= entries_.length) || (com.pullenti.unisharp.Utils.stringsNe(entries_[index].getName(), entry.getName()))) {
            index = this.findEntry(entry.getName(), true);
            if (index < 0) 
                throw new Exception("Entry cannot be found");
        }
        return this.getInputStream0(index);
    }

    public com.pullenti.unisharp.Stream getInputStream0(int entryIndex) throws Exception, Exception, IllegalArgumentException, IndexOutOfBoundsException, java.io.IOException, java.io.EOFException {
        if (isDisposed_) 
            throw new Exception("ZipFile");
        int start = this.locateEntry(entries_[entryIndex]);
        CompressionMethod method = entries_[entryIndex].get_CompressionMethod();
        com.pullenti.unisharp.Stream result = new PartialInputStream(this, start, entries_[entryIndex].getCompressedSize());
        if (entries_[entryIndex].isCrypted() == true) 
            throw new Exception("Unable to decrypt this entry");
        switch ((method).value()) { 
        case 0: // CompressionMethod.STORED 
            break;
        case 8: // CompressionMethod.DEFLATED 
            result = new InflaterInputStream(result, new Inflater(true), 4096);
            break;
        default: 
            throw new Exception(("Unsupported compression method " + method.toString()));
    }
    return result;
    }

    public boolean testArchive(boolean testData) throws Exception {
        return this.testArchiveEx(testData, TestStrategy.FINDFIRSTERROR, null);
    }

    public boolean testArchiveEx(boolean testData, TestStrategy strategy, ZipTestResultHandler resultHandler) throws Exception {
        if (isDisposed_) 
            throw new Exception("ZipFile");
        TestStatus status = new TestStatus(this);
        if (resultHandler != null) 
            resultHandler.call(status, null);
        HeaderTest test = HeaderTest.of((testData ? ((HeaderTest.HEADER.value()) | (HeaderTest.EXTRACT.value())) : HeaderTest.HEADER.value()));
        boolean testing = true;
        try {
            int entryIndex = 0;
            while (testing && ((entryIndex < this.getCount()))) {
                if (resultHandler != null) {
                    status.setEntry(this.getIndexerItem(entryIndex));
                    status.setOperation(TestOperation.ENTRYHEADER);
                    resultHandler.call(status, null);
                }
                try {
                    this.testLocalHeader(this.getIndexerItem(entryIndex), test);
                } catch (Exception ex) {
                    status.addError();
                    if (resultHandler != null) 
                        resultHandler.call(status, ("Exception during test - '" + ex.getMessage() + "'"));
                    if (strategy == TestStrategy.FINDFIRSTERROR) 
                        testing = false;
                }
                if (testing && testData && this.getIndexerItem(entryIndex).isFile()) {
                    if (resultHandler != null) {
                        status.setOperation(TestOperation.ENTRYDATA);
                        resultHandler.call(status, null);
                    }
                    Crc32 crc = new Crc32();
                    try (com.pullenti.unisharp.Stream entryStream = this.getInputStream(this.getIndexerItem(entryIndex))) {
                        byte[] buffer = new byte[4096];
                        int totalBytes = 0;
                        int bytesRead;
                        while ((((bytesRead = entryStream.read(buffer, 0, buffer.length)))) > 0) {
                            crc.updateByBufEx(buffer, 0, bytesRead);
                            if (resultHandler != null) {
                                totalBytes += bytesRead;
                                status.setBytesTested(totalBytes);
                                resultHandler.call(status, null);
                            }
                        }
                    }
                    if (Integer.compareUnsigned(this.getIndexerItem(entryIndex).getCrc(), crc.getValue()) != 0) {
                        status.addError();
                        if (resultHandler != null) 
                            resultHandler.call(status, "CRC mismatch");
                        if (strategy == TestStrategy.FINDFIRSTERROR) 
                            testing = false;
                    }
                    if (((this.getIndexerItem(entryIndex).flags & (GeneralBitFlags.DESCRIPTOR.value()))) != 0) {
                        try (ZipHelperStream helper = new ZipHelperStream(null, m_BaseStream)) {
                            DescriptorData data = new DescriptorData();
                            helper.readDataDescriptor(this.getIndexerItem(entryIndex).getLocalHeaderRequiresZip64(), data);
                            if (Integer.compareUnsigned(this.getIndexerItem(entryIndex).getCrc(), data.getCrc()) != 0) 
                                status.addError();
                            if (this.getIndexerItem(entryIndex).getCompressedSize() != data.getCompressedSize()) 
                                status.addError();
                            if (this.getIndexerItem(entryIndex).getSize() != data.getSize()) 
                                status.addError();
                        }
                    }
                }
                if (resultHandler != null) {
                    status.setOperation(TestOperation.ENTRYCOMPLETE);
                    resultHandler.call(status, null);
                }
                entryIndex += 1;
            }
            if (resultHandler != null) {
                status.setOperation(TestOperation.MISCELLANEOUSTESTS);
                resultHandler.call(status, null);
            }
        } catch (Exception ex) {
            status.addError();
            if (resultHandler != null) 
                resultHandler.call(status, ("Exception during test - '" + ex.getMessage() + "'"));
        }
        if (resultHandler != null) {
            status.setOperation(TestOperation.COMPLETE);
            status.setEntry(null);
            resultHandler.call(status, null);
        }
        return status.getErrorCount() == 0;
    }

    private int testLocalHeader(ZipEntry entry, HeaderTest tests) throws Exception, java.io.IOException, java.io.EOFException, IllegalArgumentException, IndexOutOfBoundsException {
        boolean testHeader = (((tests.value()) & (HeaderTest.HEADER.value()))) != 0;
        boolean testData = (((tests.value()) & (HeaderTest.EXTRACT.value()))) != 0;
        m_BaseStream.seek((long)(offsetOfFirstEntry + entry.getOffset()), 0);
        if (((int)this.readLEUint()) != ZipConstants.LOCALHEADERSIGNATURE) 
            throw new Exception(("Wrong local header signature @" + String.format("%X", (offsetOfFirstEntry + entry.getOffset()))));
        short extractVersion = (short)this.readLEUshort();
        short localFlags = (short)this.readLEUshort();
        short compressionMethod = (short)this.readLEUshort();
        short fileTime = (short)this.readLEUshort();
        short fileDate = (short)this.readLEUshort();
        int crcValue = this.readLEUint();
        int compressedSize = (int)this.readLEUint();
        int _size = (int)this.readLEUint();
        int storedNameLength = Short.toUnsignedInt(this.readLEUshort());
        int extraDataLength = Short.toUnsignedInt(this.readLEUshort());
        byte[] nameData = new byte[storedNameLength];
        StreamUtils.readFully(m_BaseStream, nameData);
        byte[] extraData = new byte[extraDataLength];
        StreamUtils.readFully(m_BaseStream, extraData);
        ZipExtraData localExtraData = new ZipExtraData(extraData);
        if (localExtraData.find(1)) {
            _size = localExtraData.readLong();
            compressedSize = localExtraData.readLong();
            if (((((int)localFlags) & (GeneralBitFlags.DESCRIPTOR.value()))) != 0) {
                if ((_size != -1) && (_size != entry.getSize())) 
                    throw new Exception("Size invalid for descriptor");
                if ((compressedSize != -1) && (compressedSize != entry.getCompressedSize())) 
                    throw new Exception("Compressed size invalid for descriptor");
            }
        }
        else if ((extractVersion >= ZipConstants.VERSIONZIP64) && (((Long.compareUnsigned(Integer.toUnsignedLong((int)_size), 4294967295L) == 0) || (Long.compareUnsigned(Integer.toUnsignedLong((int)compressedSize), 4294967295L) == 0)))) 
            throw new Exception("Required Zip64 extended information missing");
        if (testData) {
            if (entry.isFile()) {
                if (!entry.isCompressionMethodSupported()) 
                    throw new Exception("Compression method not supported");
                if ((extractVersion > ZipConstants.VERSIONMADEBY) || (((extractVersion > ((short)20)) && ((extractVersion < ZipConstants.VERSIONZIP64))))) 
                    throw new Exception(("Version required to extract this entry not supported (" + extractVersion + ")"));
                if (((((int)localFlags) & ((((GeneralBitFlags.PATCHED.value()) | (GeneralBitFlags.STRONGENCRYPTION.value()) | (GeneralBitFlags.ENHANCEDCOMPRESS.value())) | (GeneralBitFlags.HEADERMASKED.value()))))) != 0) 
                    throw new Exception("The library does not support the zip version required to extract this entry");
            }
        }
        if (testHeader) {
            if ((((((((extractVersion <= ((short)63)) && (extractVersion != ((short)10)) && (extractVersion != ((short)11))) && (extractVersion != ((short)20)) && (extractVersion != ((short)21))) && (extractVersion != ((short)25)) && (extractVersion != ((short)27))) && (extractVersion != ((short)45)) && (extractVersion != ((short)46))) && (extractVersion != ((short)50)) && (extractVersion != ((short)51))) && (extractVersion != ((short)52)) && (extractVersion != ((short)61))) && (extractVersion != ((short)62)) && (extractVersion != ((short)63))) 
                throw new Exception(("Version required to extract this entry is invalid (" + extractVersion + ")"));
            if (((((int)localFlags) & (((GeneralBitFlags.RESERVEDPKWARE4.value()) | (GeneralBitFlags.RESERVEDPKWARE14.value()) | (GeneralBitFlags.RESERVEDPKWARE15.value()))))) != 0) 
                throw new Exception("Reserved bit flags cannot be set.");
            if ((((((int)localFlags) & (GeneralBitFlags.ENCRYPTED.value()))) != 0) && ((extractVersion < ((short)20)))) 
                throw new Exception(("Version required to extract this entry is too low for encryption (" + extractVersion + ")"));
            if (((((int)localFlags) & (GeneralBitFlags.STRONGENCRYPTION.value()))) != 0) {
                if (((((int)localFlags) & (GeneralBitFlags.ENCRYPTED.value()))) == 0) 
                    throw new Exception("Strong encryption flag set but encryption flag is not set");
                if (extractVersion < ((short)50)) 
                    throw new Exception(("Version required to extract this entry is too low for encryption (" + extractVersion + ")"));
            }
            if ((((((int)localFlags) & (GeneralBitFlags.PATCHED.value()))) != 0) && ((extractVersion < ((short)27)))) 
                throw new Exception(("Patched data requires higher version than (" + extractVersion + ")"));
            if (localFlags != entry.flags) 
                throw new Exception("Central header/local header flags mismatch");
            if (entry.get_CompressionMethod() != (CompressionMethod.of((int)compressionMethod))) 
                throw new Exception("Central header/local header compression method mismatch");
            if (entry.getVersion() != extractVersion) 
                throw new Exception("Extract version mismatch");
            if (((((int)localFlags) & (GeneralBitFlags.STRONGENCRYPTION.value()))) != 0) {
                if (extractVersion < ((short)62)) 
                    throw new Exception("Strong encryption flag set but version not high enough");
            }
            if (((((int)localFlags) & (GeneralBitFlags.HEADERMASKED.value()))) != 0) {
                if ((fileTime != ((short)0)) || (fileDate != ((short)0))) 
                    throw new Exception("Header masked set but date/time values non-zero");
            }
            if (((((int)localFlags) & (GeneralBitFlags.DESCRIPTOR.value()))) == 0) {
                if (Integer.compareUnsigned(crcValue, entry.getCrc()) != 0) 
                    throw new Exception("Central header/local header crc mismatch");
            }
            if ((_size == 0) && (compressedSize == 0)) {
                if (Integer.compareUnsigned(crcValue, (int)0) != 0) 
                    throw new Exception("Invalid CRC for empty entry");
            }
            if (entry.getName().length() > storedNameLength) 
                throw new Exception("File name length mismatch");
            String localName = ZipConstants.convertToStringExt0((int)localFlags, nameData);
            if (com.pullenti.unisharp.Utils.stringsNe(localName, entry.getName())) 
                throw new Exception("Central header and local header file name mismatch");
            if (entry.isDirectory()) {
                if (_size > 0) 
                    throw new Exception("Directory cannot have size");
                if (entry.isCrypted()) {
                    if (compressedSize > (ZipConstants.CRYPTOHEADERSIZE + 2)) 
                        throw new Exception("Directory compressed size invalid");
                }
                else if (compressedSize > 2) 
                    throw new Exception("Directory compressed size invalid");
            }
            if (!ZipNameTransform.isValidNameEx(localName, true)) 
                throw new Exception("Name is invalid");
        }
        if ((((((int)localFlags) & (GeneralBitFlags.DESCRIPTOR.value()))) == 0) || (((_size > 0) || (compressedSize > 0)))) {
        }
        int extraLength = storedNameLength + extraDataLength;
        return (offsetOfFirstEntry + entry.getOffset() + ZipConstants.LOCALHEADERBASESIZE) + extraLength;
    }

    private static final int defaultBufferSize = 4096;

    public INameTransform getNameTransform() {
        return updateEntryFactory_.getNameTransform();
    }

    public INameTransform setNameTransform(INameTransform value) {
        updateEntryFactory_.setNameTransform(value);
        return value;
    }


    public IEntryFactory getEntryFactory() {
        return updateEntryFactory_;
    }

    public IEntryFactory setEntryFactory(IEntryFactory value) {
        if (value == null) 
            updateEntryFactory_ = new ZipEntryFactory(ZipEntryFactory.TimeSetting.LASTWRITETIME);
        else 
            updateEntryFactory_ = value;
        return value;
    }


    public int getBufferSize() {
        return bufferSize_;
    }

    public int setBufferSize(int value) throws IndexOutOfBoundsException {
        if (value < 1024) 
            throw new IndexOutOfBoundsException("value" + " " + "cannot be below 1024");
        if (bufferSize_ != value) {
            bufferSize_ = value;
            copyBuffer_ = null;
        }
        return value;
    }


    public boolean isUpdating() {
        return m_Updates != null;
    }


    public UseZip64 getUseZip64() {
        return useZip64_;
    }

    public UseZip64 setUseZip64(UseZip64 value) {
        useZip64_ = value;
        return value;
    }


    public void beginUpdateEx(IArchiveStorage archiveStorage, IDynamicDataSource dataSource) throws IllegalArgumentException, Exception, Exception {
        if (archiveStorage == null) 
            throw new IllegalArgumentException("archiveStorage");
        if (dataSource == null) 
            throw new IllegalArgumentException("dataSource");
        if (isDisposed_) 
            throw new Exception("ZipFile");
        if (isEmbeddedArchive()) 
            throw new Exception("Cannot update embedded/SFX archives");
        archiveStorage_ = archiveStorage;
        updateDataSource_ = dataSource;
        updateIndex_ = new java.util.HashMap<Object, Object>();
        if (entries_ == null) 
            entries_ = new ZipEntry[0];
        m_Updates = new java.util.ArrayList<Object>(entries_.length);
        for (ZipEntry entry : entries_) {
            int index = m_Updates.size();
            m_Updates.add(new ZipUpdate(entry, UpdateCommand.COPY, null, null, null));
            updateIndex_.put(entry.getName(), index);
        }
        UpdateComparer cmp = new UpdateComparer();
        for (int ii = 0; ii < m_Updates.size(); ii++) {
            boolean ch = false;
            for (int jj = 0; jj < (m_Updates.size() - 1); jj++) {
                if (cmp.compare(m_Updates.get(jj), m_Updates.get(jj + 1)) > 0) {
                    Object u = m_Updates.get(jj);
                    com.pullenti.unisharp.Utils.putArrayValue(m_Updates, jj, m_Updates.get(jj + 1));
                    com.pullenti.unisharp.Utils.putArrayValue(m_Updates, jj + 1, u);
                    ch = true;
                }
            }
            if (!ch) 
                break;
        }
        int idx = 0;
        for (Object up : m_Updates) {
            ZipUpdate update = (ZipUpdate)com.pullenti.unisharp.Utils.cast(up, ZipUpdate.class);
            if (up == null) 
                continue;
            if (idx == (m_Updates.size() - 1)) 
                break;
            update.setOffsetBasedSize(((ZipUpdate)m_Updates.get(idx + 1)).getEntry().getOffset() - update.getEntry().getOffset());
            idx++;
        }
        updateCount_ = m_Updates.size();
        contentsEdited_ = false;
        commentEdited_ = false;
        newComment_ = null;
    }

    public void beginUpdate(IArchiveStorage archiveStorage) throws IllegalArgumentException, Exception, Exception {
        this.beginUpdateEx(archiveStorage, new DynamicDiskDataSource());
    }

    public void beginUpdate0() throws IllegalArgumentException, Exception, Exception {
        if (getName() == null) 
            this.beginUpdateEx(new MemoryArchiveStorage(FileUpdateMode.DIRECT), new DynamicDiskDataSource());
        else 
            this.beginUpdateEx(new DiskArchiveStorage(this, FileUpdateMode.SAFE), new DynamicDiskDataSource());
    }

    public void commitUpdate() throws Exception, Exception, UnsupportedOperationException, java.io.FileNotFoundException, java.io.IOException, IllegalArgumentException, IllegalArgumentException, java.io.EOFException, IndexOutOfBoundsException {
        if (isDisposed_) 
            throw new Exception("ZipFile");
        this.checkUpdating();
        try {
            updateIndex_.clear();
            updateIndex_ = null;
            if (contentsEdited_) 
                this.runUpdates();
            else if (commentEdited_) 
                this.updateCommentOnly();
            else if (entries_.length == 0) {
                byte[] theComment = (newComment_ != null ? newComment_.getRawComment() : ZipConstants.convertToArrayStr(comment_));
                try (ZipHelperStream zhs = new ZipHelperStream(null, m_BaseStream)) {
                    zhs.writeEndOfCentralDirectory(0, 0, 0, theComment);
                }
            }
        } finally {
            this.postUpdateCleanup();
        }
    }

    public void abortUpdate() throws Exception {
        this.postUpdateCleanup();
    }

    public void setComment(String comment) throws Exception, Exception, UnsupportedOperationException {
        if (isDisposed_) 
            throw new Exception("ZipFile");
        this.checkUpdating();
        newComment_ = new ZipString(comment);
        if (newComment_.getRawLength() > 0xffff) {
            newComment_ = null;
            throw new Exception("Comment length exceeds maximum - 65535");
        }
        commentEdited_ = true;
    }

    private void addUpdate(ZipUpdate update) {
        contentsEdited_ = true;
        int index = this.findExistingUpdate(update.getEntry().getName());
        if (index >= 0) {
            if (m_Updates.get(index) == null) 
                updateCount_ += 1;
            com.pullenti.unisharp.Utils.putArrayValue(m_Updates, index, update);
        }
        else {
            index = m_Updates.size();
            m_Updates.add(update);
            updateCount_ += 1;
            updateIndex_.put(update.getEntry().getName(), index);
        }
    }

    public void add(String fileName, String entryName) throws IllegalArgumentException, UnsupportedOperationException {
        if (fileName == null) 
            throw new IllegalArgumentException("fileName");
        if (entryName == null) 
            throw new IllegalArgumentException("entryName");
        this.checkUpdating();
        this.addUpdate(new ZipUpdate(getEntryFactory().makeFileEntryEx(entryName, false), UpdateCommand.ADD, null, fileName, null));
    }

    public void addStream(com.pullenti.unisharp.Stream str, String entryName) throws IllegalArgumentException, UnsupportedOperationException {
        if (entryName == null) 
            throw new IllegalArgumentException("entryName");
        this.checkUpdating();
        this.addUpdate(new ZipUpdate(getEntryFactory().makeFileEntryEx(entryName, false), UpdateCommand.ADD, null, null, str));
    }

    public void addDirectory(String directoryName) throws IllegalArgumentException, UnsupportedOperationException {
        if (directoryName == null) 
            throw new IllegalArgumentException("directoryName");
        this.checkUpdating();
        ZipEntry dirEntry = getEntryFactory().makeDirectoryEntry(directoryName);
        this.addUpdate(new ZipUpdate(dirEntry, UpdateCommand.ADD, null, null, null));
    }

    public boolean delete(String fileName) throws IllegalArgumentException, Exception, UnsupportedOperationException {
        if (fileName == null) 
            throw new IllegalArgumentException("fileName");
        this.checkUpdating();
        boolean result = false;
        int index = this.findExistingUpdate(fileName);
        if ((index >= 0) && (m_Updates.get(index) != null)) {
            result = true;
            contentsEdited_ = true;
            com.pullenti.unisharp.Utils.putArrayValue(m_Updates, index, null);
            updateCount_ -= 1;
        }
        else 
            throw new Exception("Cannot find entry to delete");
        return result;
    }

    private void writeLEShort(int value) throws java.io.IOException {
        m_BaseStream.write((byte)((value & 0xff)));
        m_BaseStream.write((byte)((((value >> 8)) & 0xff)));
    }

    private void writeLEUshort(short value) throws java.io.IOException {
        m_BaseStream.write((byte)(((Short.toUnsignedInt(value)) & 0xff)));
        m_BaseStream.write((byte)(((Short.toUnsignedInt(value)) >>> 8)));
    }

    private void writeLEInt(int value) throws java.io.IOException {
        this.writeLEShort(value & 0xffff);
        this.writeLEShort(value >> 16);
    }

    private void writeLEUint(int value) throws java.io.IOException {
        this.writeLEUshort((short)((((int)value) & 0xffff)));
        this.writeLEUshort((short)((((int)value) >>> 16)));
    }

    private void writeLeLong(int value1, int value2) throws java.io.IOException {
        this.writeLEInt(value1);
        this.writeLEInt(value2);
    }

    private void writeLEUlong(int value1, int value2) throws java.io.IOException {
        this.writeLEUint(value1);
        this.writeLEUint(value2);
    }

    private void writeLocalEntryHeader(ZipUpdate update) throws Exception, java.io.IOException, UnsupportedOperationException, IndexOutOfBoundsException {
        ZipEntry entry = update.getOutEntry();
        entry.setOffset((int)m_BaseStream.getPosition());
        if (update.getCommand() != UpdateCommand.COPY) {
            if (entry.get_CompressionMethod() == CompressionMethod.DEFLATED) {
                if (entry.getSize() == 0) {
                    entry.setCompressedSize(entry.getSize());
                    entry.setCrc((int)0);
                    entry.set_CompressionMethod(CompressionMethod.STORED);
                }
            }
            else if (entry.get_CompressionMethod() == CompressionMethod.STORED) 
                entry.flags &= (~(GeneralBitFlags.DESCRIPTOR.value()));
            entry.setCrypted(false);
            switch ((useZip64_).value()) { 
            case 2: // UseZip64.DYNAMIC 
                if (entry.getSize() < 0) 
                    entry.forceZip64();
                break;
            case 1: // UseZip64.ON 
                entry.forceZip64();
                break;
            case 0: // UseZip64.OFF 
                break;
        }
        }
        this.writeLEInt(ZipConstants.LOCALHEADERSIGNATURE);
        this.writeLEShort(entry.getVersion());
        this.writeLEShort(entry.flags);
        this.writeLEShort(Byte.toUnsignedInt((byte)entry.get_CompressionMethod().value()));
        this.writeLEInt((int)entry.getDosTime());
        if (!entry.getHasCrc()) {
            update.setCrcPatchOffset((int)m_BaseStream.getPosition());
            this.writeLEInt(0);
        }
        else 
            this.writeLEInt((int)entry.getCrc());
        if (entry.getLocalHeaderRequiresZip64()) {
            this.writeLEInt(-1);
            this.writeLEInt(-1);
        }
        else {
            if (((entry.getCompressedSize() < 0)) || ((entry.getSize() < 0))) 
                update.setSizePatchOffset((int)m_BaseStream.getPosition());
            this.writeLEInt(entry.getCompressedSize());
            this.writeLEInt(entry.getSize());
        }
        byte[] _name = ZipConstants.convertToArray(entry.flags, entry.getName());
        if (_name.length > 0xFFFF) 
            throw new Exception("Entry name too long.");
        ZipExtraData ed = new ZipExtraData(entry.getExtraData());
        if (entry.getLocalHeaderRequiresZip64()) {
            ed.startNewEntry();
            ed.addLeLong(entry.getSize(), 0);
            ed.addLeLong(entry.getCompressedSize(), 0);
            ed.addNewEntry(1);
        }
        else 
            ed.delete(1);
        entry.setExtraData(ed.getEntryData());
        this.writeLEShort(_name.length);
        this.writeLEShort(entry.getExtraData().length);
        if (_name.length > 0) 
            m_BaseStream.write(_name, 0, _name.length);
        if (entry.getLocalHeaderRequiresZip64()) {
            if (!ed.find(1)) 
                throw new Exception("Internal error cannot find extra data");
            update.setSizePatchOffset(((int)m_BaseStream.getPosition()) + ed.getCurrentReadIndex());
        }
        if (entry.getExtraData().length > 0) 
            m_BaseStream.write(entry.getExtraData(), 0, entry.getExtraData().length);
    }

    private int writeCentralDirectoryHeader(ZipEntry entry) throws Exception, java.io.IOException, IndexOutOfBoundsException {
        if (entry.getCompressedSize() < 0) 
            throw new Exception("Attempt to write central directory entry with unknown csize");
        if (entry.getSize() < 0) 
            throw new Exception("Attempt to write central directory entry with unknown size");
        this.writeLEInt(ZipConstants.CENTRALHEADERSIGNATURE);
        this.writeLEShort(ZipConstants.VERSIONMADEBY);
        this.writeLEShort(entry.getVersion());
        this.writeLEShort(entry.flags);
        /* unchecked C# block !!! */ {
            this.writeLEShort(Byte.toUnsignedInt((byte)entry.get_CompressionMethod().value()));
            this.writeLEInt((int)entry.getDosTime());
            this.writeLEInt((int)entry.getCrc());
        }
        if (entry.isZip64Forced()) 
            this.writeLEInt(-1);
        else 
            this.writeLEInt((entry.getCompressedSize() & 0xffffffff));
        if (entry.isZip64Forced()) 
            this.writeLEInt(-1);
        else 
            this.writeLEInt(entry.getSize());
        byte[] _name = ZipConstants.convertToArray(entry.flags, entry.getName());
        if (_name.length > 0xFFFF) 
            throw new Exception("Entry name is too long.");
        this.writeLEShort(_name.length);
        ZipExtraData ed = new ZipExtraData(entry.getExtraData());
        if (entry.getCentralHeaderRequiresZip64()) {
            ed.startNewEntry();
            if (useZip64_ == UseZip64.ON) 
                ed.addLeLong(entry.getSize(), 0);
            if (useZip64_ == UseZip64.ON) 
                ed.addLeLong(entry.getCompressedSize(), 0);
            ed.addNewEntry(1);
        }
        else 
            ed.delete(1);
        byte[] centralExtraData = ed.getEntryData();
        this.writeLEShort(centralExtraData.length);
        this.writeLEShort((entry.getComment() != null ? entry.getComment().length() : 0));
        this.writeLEShort(0);
        this.writeLEShort(0);
        if (entry.getExternalFileAttributes() != -1) 
            this.writeLEInt(entry.getExternalFileAttributes());
        else if (entry.isDirectory()) 
            this.writeLEUint((int)16);
        else 
            this.writeLEUint((int)0);
        this.writeLEUint((int)entry.getOffset());
        if (_name.length > 0) 
            m_BaseStream.write(_name, 0, _name.length);
        if (centralExtraData.length > 0) 
            m_BaseStream.write(centralExtraData, 0, centralExtraData.length);
        byte[] rawComment = (entry.getComment() != null ? com.pullenti.util.MiscHelper.encodeStringAscii(entry.getComment()) : new byte[0]);
        if (rawComment.length > 0) 
            m_BaseStream.write(rawComment, 0, rawComment.length);
        return (ZipConstants.CENTRALHEADERBASESIZE + _name.length + centralExtraData.length) + rawComment.length;
    }

    private void postUpdateCleanup() throws Exception {
        updateDataSource_ = null;
        m_Updates = null;
        updateIndex_ = null;
        if (archiveStorage_ != null) {
            archiveStorage_.close();
            archiveStorage_ = null;
        }
    }

    private String getTransformedFileName(String _name) {
        INameTransform transform = getNameTransform();
        return (transform != null ? transform.transformFile(_name) : _name);
    }

    private String getTransformedDirectoryName(String _name) {
        INameTransform transform = getNameTransform();
        return (transform != null ? transform.transformDirectory(_name) : _name);
    }

    private byte[] getBuffer() {
        if (copyBuffer_ == null) 
            copyBuffer_ = new byte[bufferSize_];
        return copyBuffer_;
    }

    private void copyDescriptorBytes(ZipUpdate update, com.pullenti.unisharp.Stream dest, com.pullenti.unisharp.Stream source) throws Exception, java.io.IOException {
        int bytesToCopy = this.getDescriptorSize(update);
        if (bytesToCopy > 0) {
            byte[] buffer = this.getBuffer();
            while (bytesToCopy > 0) {
                int readSize = Math.min(buffer.length, bytesToCopy);
                int bytesRead = source.read(buffer, 0, readSize);
                if (bytesRead > 0) {
                    dest.write(buffer, 0, bytesRead);
                    bytesToCopy -= bytesRead;
                }
                else 
                    throw new Exception("Unxpected end of stream");
            }
        }
    }

    private void copyBytes(ZipUpdate update, com.pullenti.unisharp.Stream destination, com.pullenti.unisharp.Stream source, int bytesToCopy, boolean updateCrc) throws UnsupportedOperationException, Exception, java.io.IOException {
        if (destination == source) 
            throw new UnsupportedOperationException("Destination and source are the same");
        Crc32 crc = new Crc32();
        byte[] buffer = this.getBuffer();
        int targetBytes = bytesToCopy;
        int totalBytesRead = 0;
        int bytesRead;
        do{
            int readSize = buffer.length;
            if (bytesToCopy < readSize) 
                readSize = bytesToCopy;
            bytesRead = source.read(buffer, 0, readSize);
            if (bytesRead > 0) {
                if (updateCrc) 
                    crc.updateByBufEx(buffer, 0, bytesRead);
                destination.write(buffer, 0, bytesRead);
                bytesToCopy -= bytesRead;
                totalBytesRead += bytesRead;
            }
        } while ((bytesRead > 0) && (bytesToCopy > 0)); 
        if (totalBytesRead != targetBytes) 
            throw new Exception(("Failed to copy bytes expected " + targetBytes + " read " + totalBytesRead));
        if (updateCrc) 
            update.getOutEntry().setCrc(crc.getValue());
    }

    private int getDescriptorSize(ZipUpdate update) {
        int result = 0;
        if (((update.getEntry().flags & (GeneralBitFlags.DESCRIPTOR.value()))) != 0) {
            result = ZipConstants.DATADESCRIPTORSIZE - 4;
            if (update.getEntry().getLocalHeaderRequiresZip64()) 
                result = ZipConstants.ZIP64DATADESCRIPTORSIZE - 4;
        }
        return result;
    }

    private void copyDescriptorBytesDirect(ZipUpdate update, com.pullenti.unisharp.Stream stream, com.pullenti.unisharp.Outargwrapper<Integer> destinationPosition, int sourcePosition) throws Exception, java.io.IOException {
        int bytesToCopy = this.getDescriptorSize(update);
        while (bytesToCopy > 0) {
            int readSize = bytesToCopy;
            byte[] buffer = this.getBuffer();
            stream.setPosition((long)sourcePosition);
            int bytesRead = stream.read(buffer, 0, readSize);
            if (bytesRead > 0) {
                stream.setPosition((long)destinationPosition.value);
                stream.write(buffer, 0, bytesRead);
                bytesToCopy -= bytesRead;
                destinationPosition.value += bytesRead;
                sourcePosition += bytesRead;
            }
            else 
                throw new Exception("Unxpected end of stream");
        }
    }

    private void copyEntryDataDirect(ZipUpdate update, com.pullenti.unisharp.Stream stream, boolean updateCrc, com.pullenti.unisharp.Outargwrapper<Integer> destinationPosition, com.pullenti.unisharp.Outargwrapper<Integer> sourcePosition) throws Exception, java.io.IOException {
        int bytesToCopy = update.getEntry().getCompressedSize();
        Crc32 crc = new Crc32();
        byte[] buffer = this.getBuffer();
        int targetBytes = bytesToCopy;
        int totalBytesRead = 0;
        int bytesRead;
        do{
            int readSize = buffer.length;
            if (bytesToCopy < readSize) 
                readSize = bytesToCopy;
            stream.setPosition((long)sourcePosition.value);
            bytesRead = stream.read(buffer, 0, readSize);
            if (bytesRead > 0) {
                if (updateCrc) 
                    crc.updateByBufEx(buffer, 0, bytesRead);
                stream.setPosition((long)destinationPosition.value);
                stream.write(buffer, 0, bytesRead);
                destinationPosition.value += bytesRead;
                sourcePosition.value += bytesRead;
                bytesToCopy -= bytesRead;
                totalBytesRead += bytesRead;
            }
        } while ((bytesRead > 0) && (bytesToCopy > 0)); 
        if (totalBytesRead != targetBytes) 
            throw new Exception(("Failed to copy bytes expected " + targetBytes + " read " + totalBytesRead));
        if (updateCrc) 
            update.getOutEntry().setCrc(crc.getValue());
    }

    private int findExistingUpdate(String fileName) {
        int result = -1;
        String convertedName = this.getTransformedFileName(fileName);
        if (updateIndex_.containsKey(convertedName)) 
            result = (int)updateIndex_.get(convertedName);
        return result;
    }

    private com.pullenti.unisharp.Stream getOutputStream(ZipEntry entry) throws Exception {
        com.pullenti.unisharp.Stream result = m_BaseStream;
        if (entry.isCrypted() == true) 
            throw new Exception("Encryption not supported");
        switch ((entry.get_CompressionMethod()).value()) { 
        case 0: // CompressionMethod.STORED 
            return new UncompressedStream(result);
        case 8: // CompressionMethod.DEFLATED 
            DeflaterOutputStream dos = new DeflaterOutputStream(result, new Deflater(9, true), 512);
            dos.setStreamOwner(false);
            return dos;
        default: 
            throw new Exception(("Unknown compression method " + entry.get_CompressionMethod().toString()));
    }
    }

    private void addEntry(ZipFile workFile, ZipUpdate update) throws Exception, java.io.IOException, UnsupportedOperationException, java.io.FileNotFoundException, IllegalArgumentException, UnsupportedOperationException, IndexOutOfBoundsException {
        com.pullenti.unisharp.Stream source = null;
        try {
            if (update.getEntry().isFile()) {
                source = update.getSource();
                if (source == null) 
                    source = updateDataSource_.getSource(update.getEntry(), update.getFilename());
            }
            if (source != null) {
                int sourceStreamLength = (int)source.length();
                if (update.getOutEntry().getSize() < 0) 
                    update.getOutEntry().setSize(sourceStreamLength);
                else if (update.getOutEntry().getSize() != sourceStreamLength) {
                }
                workFile.writeLocalEntryHeader(update);
                int dataStart = (int)workFile.m_BaseStream.getPosition();
                try (com.pullenti.unisharp.Stream output = workFile.getOutputStream(update.getOutEntry())) {
                    this.copyBytes(update, output, source, sourceStreamLength, true);
                }
                int dataEnd = (int)workFile.m_BaseStream.getPosition();
                update.getOutEntry().setCompressedSize(dataEnd - dataStart);
                if (((update.getOutEntry().flags & (GeneralBitFlags.DESCRIPTOR.value()))) == (GeneralBitFlags.DESCRIPTOR.value())) {
                    try (ZipHelperStream helper = new ZipHelperStream(null, workFile.m_BaseStream)) {
                        helper.writeDataDescriptor(update.getOutEntry());
                    }
                }
            }
            else {
                workFile.writeLocalEntryHeader(update);
                update.getOutEntry().setCompressedSize(0);
            }
        } finally {
            if (source != null) 
                source.close();
        }
    }

    private void modifyEntry(ZipFile workFile, ZipUpdate update) throws Exception, java.io.IOException, UnsupportedOperationException, UnsupportedOperationException, IndexOutOfBoundsException, IllegalArgumentException, Exception, java.io.EOFException {
        workFile.writeLocalEntryHeader(update);
        int dataStart = (int)workFile.m_BaseStream.getPosition();
        if (update.getEntry().isFile() && (update.getFilename() != null)) {
            try (com.pullenti.unisharp.Stream output = workFile.getOutputStream(update.getOutEntry())) {
                try (com.pullenti.unisharp.Stream source = this.getInputStream(update.getEntry())) {
                    this.copyBytes(update, output, source, (int)source.length(), true);
                }
            }
        }
        int dataEnd = (int)workFile.m_BaseStream.getPosition();
        update.getEntry().setCompressedSize(dataEnd - dataStart);
    }

    private void copyEntryDirect(ZipFile workFile, ZipUpdate update, com.pullenti.unisharp.Outargwrapper<Integer> destinationPosition) throws java.io.IOException, java.io.EOFException, Exception, UnsupportedOperationException, IndexOutOfBoundsException {
        boolean skipOver = false;
        if (update.getEntry().getOffset() == destinationPosition.value) 
            skipOver = true;
        if (!skipOver) {
            m_BaseStream.setPosition((long)destinationPosition.value);
            workFile.writeLocalEntryHeader(update);
            destinationPosition.value = (int)m_BaseStream.getPosition();
        }
        int sourcePosition = 0;
        int nameLengthOffset = 26;
        int entryDataOffset = update.getEntry().getOffset() + nameLengthOffset;
        m_BaseStream.seek((long)entryDataOffset, 0);
        int nameLength = Short.toUnsignedInt(this.readLEUshort());
        int extraLength = Short.toUnsignedInt(this.readLEUshort());
        sourcePosition = ((int)m_BaseStream.getPosition()) + ((int)nameLength) + ((int)extraLength);
        if (skipOver) {
            if (update.getOffsetBasedSize() != -1) 
                destinationPosition.value += update.getOffsetBasedSize();
            else 
                destinationPosition.value += ((((sourcePosition - entryDataOffset)) + nameLengthOffset + update.getEntry().getCompressedSize()) + this.getDescriptorSize(update));
        }
        else {
            if (update.getEntry().getCompressedSize() > 0) {
                com.pullenti.unisharp.Outargwrapper<Integer> wrapsourcePosition563 = new com.pullenti.unisharp.Outargwrapper<Integer>(sourcePosition);
                this.copyEntryDataDirect(update, m_BaseStream, false, destinationPosition, wrapsourcePosition563);
                sourcePosition = (wrapsourcePosition563.value != null ? wrapsourcePosition563.value : 0);
            }
            this.copyDescriptorBytesDirect(update, m_BaseStream, destinationPosition, sourcePosition);
        }
    }

    private void copyEntry(ZipFile workFile, ZipUpdate update) throws java.io.IOException, java.io.EOFException, UnsupportedOperationException, Exception, UnsupportedOperationException, IndexOutOfBoundsException {
        workFile.writeLocalEntryHeader(update);
        if (update.getEntry().getCompressedSize() > 0) {
            int nameLengthOffset = 26;
            int entryDataOffset = (update.getEntry().getOffset()) + nameLengthOffset;
            m_BaseStream.seek((long)entryDataOffset, 0);
            int nameLength = Short.toUnsignedInt(this.readLEUshort());
            int extraLength = Short.toUnsignedInt(this.readLEUshort());
            m_BaseStream.seek((long)(((int)nameLength) + ((int)extraLength)), 1);
            this.copyBytes(update, workFile.m_BaseStream, m_BaseStream, update.getEntry().getCompressedSize(), false);
        }
        this.copyDescriptorBytes(update, workFile.m_BaseStream, m_BaseStream);
    }

    private void reopen(com.pullenti.unisharp.Stream source) throws Exception, java.io.IOException, java.io.EOFException, IllegalArgumentException, IndexOutOfBoundsException, IllegalArgumentException, java.io.FileNotFoundException {
        if (source == null) 
            throw new Exception("Failed to reopen archive - no source");
        isNewArchive_ = false;
        m_BaseStream = source;
        this.readEntries();
    }

    private void updateCommentOnly() throws Exception, java.io.IOException, java.io.FileNotFoundException, java.io.EOFException, IllegalArgumentException, IndexOutOfBoundsException, IllegalArgumentException {
        int baseLength = (int)m_BaseStream.length();
        ZipHelperStream updateFile = null;
        if (archiveStorage_.getUpdateMode() == FileUpdateMode.SAFE) {
            try (com.pullenti.unisharp.Stream copyStream = archiveStorage_.makeTemporaryCopy(m_BaseStream)) {
                updateFile = new ZipHelperStream(null, copyStream);
                updateFile.setStreamOwner(true);
                m_BaseStream.close();
                m_BaseStream = null;
            }
        }
        else if (archiveStorage_.getUpdateMode() == FileUpdateMode.DIRECT) {
            m_BaseStream = archiveStorage_.openForDirectUpdate(m_BaseStream);
            updateFile = new ZipHelperStream(null, m_BaseStream);
        }
        else {
            m_BaseStream.close();
            m_BaseStream = null;
            updateFile = new ZipHelperStream(getName(), null);
        }
        try {
            int locatedCentralDirOffset = updateFile.locateBlockWithSignature(ZipConstants.ENDOFCENTRALDIRECTORYSIGNATURE, baseLength, ZipConstants.ENDOFCENTRALRECORDBASESIZE, 0xffff);
            if (locatedCentralDirOffset < 0) 
                throw new Exception("Cannot find central directory");
            int centralHeaderCommentSizeOffset = 16;
            updateFile.setPosition(updateFile.getPosition() + ((long)centralHeaderCommentSizeOffset));
            byte[] rawComment = newComment_.getRawComment();
            updateFile.writeLEShort(rawComment.length);
            updateFile.write(rawComment, 0, rawComment.length);
            updateFile.setLength(updateFile.getPosition());
        } finally {
            updateFile.close();
        }
        if (archiveStorage_.getUpdateMode() == FileUpdateMode.SAFE) 
            this.reopen(archiveStorage_.convertTemporaryToFinal());
        else 
            this.readEntries();
    }

    /**
     * Class used to sort updates.
     */
    public static class UpdateComparer implements java.util.Comparator<Object> {
    
        /**
         * Compares two objects and returns a value indicating whether one is 
         * less than, equal to or greater than the other.
         * @param x First object to compare
         * @param y Second object to compare.
         * @return Compare result.
         */
        @Override
        public int compare(Object x, Object y) {
            com.pullenti.unitext.internal.zip.ZipUpdate zx = (com.pullenti.unitext.internal.zip.ZipUpdate)com.pullenti.unisharp.Utils.cast(x, com.pullenti.unitext.internal.zip.ZipUpdate.class);
            com.pullenti.unitext.internal.zip.ZipUpdate zy = (com.pullenti.unitext.internal.zip.ZipUpdate)com.pullenti.unisharp.Utils.cast(y, com.pullenti.unitext.internal.zip.ZipUpdate.class);
            int result;
            if (zx == null) {
                if (zy == null) 
                    result = 0;
                else 
                    result = -1;
            }
            else if (zy == null) 
                result = 1;
            else {
                int xCmdValue = (((zx.getCommand() == com.pullenti.unitext.internal.zip.UpdateCommand.COPY) || (zx.getCommand() == com.pullenti.unitext.internal.zip.UpdateCommand.MODIFY)) ? 0 : 1);
                int yCmdValue = (((zy.getCommand() == com.pullenti.unitext.internal.zip.UpdateCommand.COPY) || (zy.getCommand() == com.pullenti.unitext.internal.zip.UpdateCommand.MODIFY)) ? 0 : 1);
                result = xCmdValue - yCmdValue;
                if (result == 0) {
                    int offsetDiff = zx.getEntry().getOffset() - zy.getEntry().getOffset();
                    if (offsetDiff < 0) 
                        result = -1;
                    else if (offsetDiff == 0) 
                        result = 0;
                    else 
                        result = 1;
                }
            }
            return result;
        }
        public UpdateComparer() {
        }
    }


    private void runUpdates() throws Exception, java.io.IOException, IllegalArgumentException, IllegalArgumentException, java.io.FileNotFoundException, java.io.EOFException, IndexOutOfBoundsException {
        int sizeEntries = 0;
        int endOfStream = 0;
        boolean directUpdate = false;
        int destinationPosition = 0;
        ZipFile workFile;
        if (isNewArchive()) {
            workFile = this;
            workFile.m_BaseStream.setPosition(0L);
            directUpdate = true;
        }
        else if (archiveStorage_.getUpdateMode() == FileUpdateMode.DIRECT) {
            workFile = this;
            workFile.m_BaseStream.setPosition(0L);
            directUpdate = true;
            UpdateComparer cmp = new UpdateComparer();
            for (int ii = 0; ii < m_Updates.size(); ii++) {
                boolean ch = false;
                for (int jj = 0; jj < (m_Updates.size() - 1); jj++) {
                    if (cmp.compare(m_Updates.get(jj), m_Updates.get(jj + 1)) > 0) {
                        Object u = m_Updates.get(jj);
                        com.pullenti.unisharp.Utils.putArrayValue(m_Updates, jj, m_Updates.get(jj + 1));
                        com.pullenti.unisharp.Utils.putArrayValue(m_Updates, jj + 1, u);
                        ch = true;
                    }
                }
                if (!ch) 
                    break;
            }
        }
        else {
            workFile = ZipFile.createStream(archiveStorage_.getTemporaryOutput());
            workFile.setUseZip64(this.getUseZip64());
        }
        try {
            for (Object up : m_Updates) {
                ZipUpdate update = (ZipUpdate)com.pullenti.unisharp.Utils.cast(up, ZipUpdate.class);
                if (update != null) {
                    switch ((update.getCommand()).value()) { 
                    case 0: // UpdateCommand.COPY 
                        if (directUpdate) {
                            com.pullenti.unisharp.Outargwrapper<Integer> wrapdestinationPosition564 = new com.pullenti.unisharp.Outargwrapper<Integer>(destinationPosition);
                            this.copyEntryDirect(workFile, update, wrapdestinationPosition564);
                            destinationPosition = (wrapdestinationPosition564.value != null ? wrapdestinationPosition564.value : 0);
                        }
                        else 
                            this.copyEntry(workFile, update);
                        break;
                    case 1: // UpdateCommand.MODIFY 
                        this.modifyEntry(workFile, update);
                        break;
                    case 2: // UpdateCommand.ADD 
                        if (!isNewArchive() && directUpdate) 
                            workFile.m_BaseStream.setPosition((long)destinationPosition);
                        this.addEntry(workFile, update);
                        if (directUpdate) 
                            destinationPosition = (int)workFile.m_BaseStream.getPosition();
                        break;
                }
                }
            }
            if (!isNewArchive() && directUpdate) 
                workFile.m_BaseStream.setPosition((long)destinationPosition);
            int centralDirOffset = (int)workFile.m_BaseStream.getPosition();
            for (Object up : m_Updates) {
                ZipUpdate update = (ZipUpdate)com.pullenti.unisharp.Utils.cast(up, ZipUpdate.class);
                if (update != null) 
                    sizeEntries += workFile.writeCentralDirectoryHeader(update.getOutEntry());
            }
            byte[] theComment = (newComment_ != null ? newComment_.getRawComment() : ZipConstants.convertToArrayStr(comment_));
            try (ZipHelperStream zhs = new ZipHelperStream(null, workFile.m_BaseStream)) {
                zhs.writeEndOfCentralDirectory(updateCount_, sizeEntries, centralDirOffset, theComment);
            }
            endOfStream = (int)workFile.m_BaseStream.getPosition();
            for (Object up : m_Updates) {
                ZipUpdate update = (ZipUpdate)com.pullenti.unisharp.Utils.cast(up, ZipUpdate.class);
                if (update != null) {
                    if ((update.getCrcPatchOffset() > 0) && (update.getOutEntry().getCompressedSize() > 0)) {
                        workFile.m_BaseStream.setPosition((long)update.getCrcPatchOffset());
                        workFile.writeLEInt((int)update.getOutEntry().getCrc());
                    }
                    if (update.getSizePatchOffset() > 0) {
                        workFile.m_BaseStream.setPosition((long)update.getSizePatchOffset());
                        if (update.getOutEntry().getLocalHeaderRequiresZip64()) {
                            workFile.writeLeLong(update.getOutEntry().getSize(), 0);
                            workFile.writeLeLong(update.getOutEntry().getCompressedSize(), 0);
                        }
                        else {
                            workFile.writeLEInt(update.getOutEntry().getCompressedSize());
                            workFile.writeLEInt(update.getOutEntry().getSize());
                        }
                    }
                }
            }
        } catch (Exception ex565) {
            if (!directUpdate && (workFile.getName() != null)) 
                (new java.io.File(workFile.getName())).delete();
            throw ex565;
        } finally {
            if (workFile != null) 
                workFile.close();
            workFile = null;
        }
        if (directUpdate) {
            if (workFile != null) {
                workFile.m_BaseStream.setLength((long)endOfStream);
                workFile.m_BaseStream.flush();
            }
            isNewArchive_ = false;
            this.readEntries();
        }
        else {
            m_BaseStream.close();
            this.reopen(archiveStorage_.convertTemporaryToFinal());
        }
    }

    private void checkUpdating() throws UnsupportedOperationException {
        if (m_Updates == null) 
            throw new UnsupportedOperationException("BeginUpdate has not been called");
    }

    private void disposeInternal(boolean disposing) throws java.io.IOException, Exception {
        if (!isDisposed_) {
            isDisposed_ = true;
            entries_ = new ZipEntry[0];
            if (isStreamOwner() && (m_BaseStream != null)) {
                m_BaseStream.close();
                m_BaseStream = null;
            }
            this.postUpdateCleanup();
        }
    }

    protected void dispose(boolean disposing) throws java.io.IOException, Exception {
        this.disposeInternal(disposing);
    }

    private short readLEUshort() throws java.io.EOFException, java.io.IOException {
        int data1 = m_BaseStream.read();
        if (data1 < 0) 
            throw new java.io.EOFException("End of stream");
        int data2 = m_BaseStream.read();
        if (data2 < 0) 
            throw new java.io.EOFException("End of stream");
        return (short)(((Short.toUnsignedInt((short)data1)) | (Short.toUnsignedInt((short)(data2 << 8)))));
    }

    private int readLEUint() throws java.io.EOFException, java.io.IOException {
        return (int)(((Short.toUnsignedInt(this.readLEUshort())) | ((Short.toUnsignedInt(this.readLEUshort())) << 16)));
    }

    private int locateBlockWithSignature(int signature, int endLocation, int minimumBlockSize, int maximumVariableData) throws Exception, java.io.FileNotFoundException, java.io.IOException, java.io.EOFException {
        try (ZipHelperStream les = new ZipHelperStream(null, m_BaseStream)) {
            return les.locateBlockWithSignature(signature, endLocation, minimumBlockSize, maximumVariableData);
        }
    }

    private void readEntries() throws Exception, java.io.IOException, java.io.EOFException, IllegalArgumentException, IndexOutOfBoundsException, IllegalArgumentException, java.io.FileNotFoundException {
        if (m_BaseStream == null) 
            return;
        if (m_BaseStream.length() == (0L)) 
            return;
        if (m_BaseStream.canSeek() == false) 
            throw new Exception("ZipFile stream must be seekable");
        int locatedEndOfCentralDir = this.locateBlockWithSignature(ZipConstants.ENDOFCENTRALDIRECTORYSIGNATURE, (int)m_BaseStream.length(), ZipConstants.ENDOFCENTRALRECORDBASESIZE, 0xffff);
        if (locatedEndOfCentralDir < 0) 
            throw new Exception("Cannot find central directory");
        short thisDiskNumber = this.readLEUshort();
        short startCentralDirDisk = this.readLEUshort();
        int entriesForThisDisk = Short.toUnsignedInt(this.readLEUshort());
        int entriesForWholeCentralDir = Short.toUnsignedInt(this.readLEUshort());
        int centralDirSize = (int)this.readLEUint();
        int offsetOfCentralDir = (int)this.readLEUint();
        int commentSize = Short.toUnsignedInt(this.readLEUshort());
        if (Integer.compareUnsigned(commentSize, (int)0) > 0) {
            byte[] comment = new byte[(int)commentSize];
            StreamUtils.readFully(m_BaseStream, comment);
            comment_ = ZipConstants.convertToString0(comment);
        }
        else 
            comment_ = "";
        boolean isZip64 = false;
        if (Integer.compareUnsigned(thisDiskNumber, (short)0xffff) == 0) {
            isZip64 = true;
            int offset = this.locateBlockWithSignature(ZipConstants.ZIP64CENTRALDIRLOCATORSIGNATURE, locatedEndOfCentralDir, 0, 0x1000);
            if (offset < 0) 
                throw new Exception("Cannot find Zip64 locator");
            this.readLEUint();
            int offset64 = (int)this.readLEUint();
            this.readLEUint();
            int totalDisks = this.readLEUint();
            m_BaseStream.setPosition((long)offset64);
            int sig64 = (int)this.readLEUint();
            if (sig64 != ZipConstants.ZIP64CENTRALFILEHEADERSIGNATURE) 
                throw new Exception(("Invalid Zip64 Central directory signature at " + String.format("%X", offset64)));
            int recordSize = this.readLEUint();
            this.readLEUint();
            int versionMadeBy = Short.toUnsignedInt(this.readLEUshort());
            int versionToExtract = Short.toUnsignedInt(this.readLEUshort());
            int thisDisk = this.readLEUint();
            int centralDirDisk = this.readLEUint();
            entriesForThisDisk = (int)this.readLEUint();
            this.readLEUint();
            entriesForWholeCentralDir = (int)this.readLEUint();
            this.readLEUint();
            centralDirSize = (int)this.readLEUint();
            this.readLEUint();
            offsetOfCentralDir = (int)this.readLEUint();
            this.readLEUint();
        }
        entries_ = new ZipEntry[entriesForThisDisk];
        if (!isZip64 && ((offsetOfCentralDir < (((long)locatedEndOfCentralDir) - (((4L) + ((long)centralDirSize))))))) {
            offsetOfFirstEntry = locatedEndOfCentralDir - ((4 + centralDirSize + offsetOfCentralDir));
            if (offsetOfFirstEntry <= 0) 
                throw new Exception("Invalid embedded zip archive");
        }
        m_BaseStream.seek((long)(offsetOfFirstEntry + offsetOfCentralDir), 0);
        for (int i = 0; i < entriesForThisDisk; i++) {
            if (((int)this.readLEUint()) != ZipConstants.CENTRALHEADERSIGNATURE) 
                throw new Exception("Wrong Central Directory signature");
            int versionMadeBy = Short.toUnsignedInt(this.readLEUshort());
            int versionToExtract = Short.toUnsignedInt(this.readLEUshort());
            int bitFlags = Short.toUnsignedInt(this.readLEUshort());
            int method = Short.toUnsignedInt(this.readLEUshort());
            int dostime = this.readLEUint();
            int crc = this.readLEUint();
            int csize = (int)this.readLEUint();
            int _size = (int)this.readLEUint();
            int nameLen = Short.toUnsignedInt(this.readLEUshort());
            int extraLen = Short.toUnsignedInt(this.readLEUshort());
            int commentLen = Short.toUnsignedInt(this.readLEUshort());
            int diskStartNo = Short.toUnsignedInt(this.readLEUshort());
            int internalAttributes = Short.toUnsignedInt(this.readLEUshort());
            int externalAttributes = this.readLEUint();
            int offset = (int)this.readLEUint();
            byte[] buffer = new byte[Math.max(nameLen, commentLen)];
            StreamUtils.readFullyEx(m_BaseStream, buffer, 0, nameLen);
            String _name = ZipConstants.convertToStringExt(bitFlags, buffer, nameLen);
            ZipEntry entry = new ZipEntry(_name, versionToExtract, versionMadeBy, CompressionMethod.of(method));
            entry.setCrc(crc);
            entry.setSize(_size);
            entry.setCompressedSize(csize);
            entry.flags = bitFlags;
            entry.setDosTime(dostime);
            entry.setZipFileIndex(i);
            entry.setOffset(offset);
            entry.setExternalFileAttributes((int)externalAttributes);
            if (((bitFlags & 8)) == 0) 
                entry.setCryptoCheckValue((byte)((((int)crc) >>> 24)));
            else 
                entry.setCryptoCheckValue((byte)((((((int)dostime) >>> 8)) & 0xff)));
            if (extraLen > 0) {
                byte[] extra = new byte[extraLen];
                StreamUtils.readFully(m_BaseStream, extra);
                entry.setExtraData(extra);
            }
            entry.processExtraData(false);
            if (commentLen > 0) {
                StreamUtils.readFullyEx(m_BaseStream, buffer, 0, commentLen);
                entry.setComment(ZipConstants.convertToStringExt(bitFlags, buffer, commentLen));
            }
            entries_[i] = entry;
        }
    }

    private int locateEntry(ZipEntry entry) throws Exception, java.io.IOException, java.io.EOFException, IllegalArgumentException, IndexOutOfBoundsException {
        return this.testLocalHeader(entry, HeaderTest.EXTRACT);
    }

    private boolean isDisposed_;

    private String name_;

    private String comment_;

    public com.pullenti.unisharp.Stream m_BaseStream;

    private boolean m_StreamOwner;

    private int offsetOfFirstEntry;

    private ZipEntry[] entries_;

    private boolean isNewArchive_;

    private UseZip64 useZip64_ = UseZip64.DYNAMIC;

    private java.util.ArrayList<Object> m_Updates;

    private int updateCount_;

    private java.util.HashMap<Object, Object> updateIndex_;

    private IArchiveStorage archiveStorage_;

    private IDynamicDataSource updateDataSource_;

    private boolean contentsEdited_;

    private int bufferSize_ = defaultBufferSize;

    private byte[] copyBuffer_;

    private ZipString newComment_;

    private boolean commentEdited_;

    private IEntryFactory updateEntryFactory_ = new ZipEntryFactory(ZipEntryFactory.TimeSetting.LASTWRITETIME);

    public static class HeaderTest implements Comparable<HeaderTest> {
    
        public static final HeaderTest EXTRACT; // 0x01
    
        public static final HeaderTest HEADER; // 0x02
    
    
        public int value() { return m_val; }
        private int m_val;
        private String m_str;
        private HeaderTest(int val, String str) { m_val = val; m_str = str; }
        @Override
        public String toString() {
            if(m_str != null) return m_str;
            return ((Integer)m_val).toString();
        }
        @Override
        public int hashCode() {
            return (int)m_val;
        }
        @Override
        public int compareTo(HeaderTest v) {
            if(m_val < v.m_val) return -1;
            if(m_val > v.m_val) return 1;
            return 0;
        }
        private static java.util.HashMap<Integer, HeaderTest> mapIntToEnum; 
        private static java.util.HashMap<String, HeaderTest> mapStringToEnum; 
        private static HeaderTest[] m_Values; 
        private static java.util.Collection<Integer> m_Keys; 
        public static HeaderTest of(int val) {
            if (mapIntToEnum.containsKey(val)) return mapIntToEnum.get(val);
            HeaderTest item = new HeaderTest(val, ((Integer)val).toString());
            mapIntToEnum.put(val, item);
            mapStringToEnum.put(item.m_str.toUpperCase(), item);
            return item; 
        }
        public static HeaderTest of(String str) {
            str = str.toUpperCase(); 
            if (mapStringToEnum.containsKey(str)) return mapStringToEnum.get(str);
            return null; 
        }
        public static boolean isDefined(Object val) {
            if(val instanceof Integer) return m_Keys.contains((Integer)val); 
            return false; 
        }
        public static HeaderTest[] getValues() {
            return m_Values; 
        }
        static {
            mapIntToEnum = new java.util.HashMap<Integer, HeaderTest>();
            mapStringToEnum = new java.util.HashMap<String, HeaderTest>();
            EXTRACT = new HeaderTest(0x01, "EXTRACT");
            mapIntToEnum.put(EXTRACT.value(), EXTRACT);
            mapStringToEnum.put(EXTRACT.m_str.toUpperCase(), EXTRACT);
            HEADER = new HeaderTest(0x02, "HEADER");
            mapIntToEnum.put(HEADER.value(), HEADER);
            mapStringToEnum.put(HEADER.m_str.toUpperCase(), HEADER);
            java.util.Collection<HeaderTest> col = mapIntToEnum.values();
            m_Values = new HeaderTest[col.size()];
            col.toArray(m_Values);
            m_Keys = new java.util.ArrayList<Integer>(mapIntToEnum.keySet());
        }
    }

    public ZipFile() {
    }
    public static ZipFile _globalInstance;
    
    static {
        try { _globalInstance = new ZipFile(); } 
        catch(Exception e) { }
    }
}
