Winzipの自己解凍(exe)zipファイルをJavaで読み込むにはどうすればよいですか?

既存のメソッドがあるか、またはデータをZipInputStreamに渡す前に手動で解析してexeブロックをスキップする必要がありますか?

7

4 答え

EXEファイル形式ZIPファイル形式とさまざまなオプションをテストするのが最も簡単な解決策は、最初のzipローカルファイルヘッダまでのプリアンブルを無視することです。

Zip file layout

Zip local file header

プリアンブルをバイパスするための入力ストリームフィルタを書きました。完全に動作します。

ZipInputStream zis = new ZipInputStream(
    new WinZipInputStream(
    new FileInputStream("test.exe")));
while ((ze = zis.getNextEntry()) != null) {
    . . .
    zis.closeEntry();
}
zis.close();

WinZipInputStream.java

import java.io.FilterInputStream;
import java.io.InputStream;
import java.io.IOException;

public class WinZipInputStream extends FilterInputStream {
    public static final byte[] ZIP_LOCAL = { 0x50, 0x4b, 0x03, 0x04 };
    protected int ip;
    protected int op;

    public WinZipInputStream(InputStream is) {
        super(is);
    }

    public int read() throws IOException {
        while(ip < ZIP_LOCAL.length) {
            int c = super.read();
            if (c == ZIP_LOCAL[ip]) {
                ip++;
            }
            else ip = 0;
        }

        if (op < ZIP_LOCAL.length)
            return ZIP_LOCAL[op++];
        else
            return super.read();
    }

    public int read(byte[] b, int off, int len) throws IOException {
        if (op == ZIP_LOCAL.length) return super.read(b, off, len);
        int l = 0;
        while (l < Math.min(len, ZIP_LOCAL.length)) {
            b[l++] = (byte)read();
        }
        return l;
    }
}
12
追加された
本当にありがとう、これは私をたくさん助けました。
追加された 著者 Nagaraj N,

ZIPファイルについての素敵な点は、それらのシーケンシャルな構造です:すべてのエントリは独立したバイトの束で、最後にファイル内のすべてのエントリとそのオフセットをリストするセントラルディレクトリインデックスです。

悪い点は、 java.util.zip。* クラスはそのインデックスを無視してファイルへの読み込みを開始し、最初のエントリがローカルファイルヘッダブロックであると考えますこれは、自己解凍式のZIPアーカイブ(これらはEXE部分から始まります)では当てはまりません。

数年前、私はCDIに依存する個々のZIPエントリ(LFH +データ)を抽出するカスタムZIPパーサーを書いて、ファイル内のどこにこれらのエントリがあるのか​​を見つけました。私はちょうどチェックして、それは実際には余分なことなく自己拡張のZIPアーカイブのエントリを一覧表示し、オフセットを与えることができる - ので、あなたはどちらかをすることができます:

  1. use that code to find the first LFH after the EXE part, and copy everything after that offset to a different File, then feed that new File to java.util.zip.ZipFile:

    Edit: Just skipping the EXE part doesn't seem to work, ZipFile still won't read it and my native ZIP program complains that the new ZIP file is damaged and exactly the number of bytes I skipped are given as "missing" (so it actually reads the CDI). I guess some headers would need to be rewritten, so the second approach given below looks more promising -- or

  2. use that code for the full ZIP extraction (it's similar to java.util.zip); this would require some additional plumbing because the code originally wasn't intended as replacement ZIP library but had a very specific use case (differential updating of ZIP files over HTTP)

このコードはSourceForge(プロジェクトページウェブサイト)、Apache License 2.0の下でライセンスされているので、商用利用は問題ありません。AFAIKには、ゲーム資産のアップデータとして商用ゲームが使用されています。

The interesting parts to get the offsets from a ZIP file are in Indexer.parseZipFile which returns a LinkedHashMap (so the first map entry has the lowest offset in the file). Here's the code I used to list the entries of a self-extracting ZIP archive (created with the WinZIP SE creator with Wine on Ubuntu from an acra release file):

public static void main(String[] args) throws Exception {
    File archive = new File("/home/phil/downloads", "acra-4.2.3.exe");
    Map resources = parseZipFile(archive);
    for (Entry resource : resources.entrySet()) {
        System.out.println(resource.getKey() + ": " + resource.getValue());
    }
}

すべてのヘッダー解析クラスを含む Indexer クラスと zip パッケージを除いて、おそらくほとんどのコードを取り除くことができます。

7
追加された
それが正しい軌道に乗ってくれたことを感謝します。私は最初のローカルヘッダブロックまで何も無視する単純な入力フィルタを書くことになりました。
追加された 著者 jamesallman,

この場合、TrueZipが最適です。 (少なくとも私の場合は)

自己解凍ジップは、次の形式のコード1のheader1 file1です(通常のzipはformat1の形式です)。コードはzipの解凍方法を示します

Truezip抽出ユーティリティは余分なバイトについて不満を持ち、例外をスローします

ここにコードがあります

 private void Extract(String src, String dst, String incPath) {
    TFile srcFile = new TFile(src, incPath);
    TFile dstFile = new TFile(dst);
    try {
        TFile.cp_rp(srcFile, dstFile, TArchiveDetector.NULL);
        } 
    catch (IOException e) {
       //Handle Exception
        }
}

このメソッドはExtract(新しいString( "C:\ 2006Production.exe")、新しいString( "c:\")、 "")のように呼び出すことができます。

ファイルはCドライブに展開されています...ファイルに対して独自の操作を実行できます。私はこれが役立つことを願っています

ありがとう。

1
追加された
なぜあなたは例外で何もしていませんか?それが真実のみである場合、なぜブール値を返すのですか?
追加された 著者 Robin Salih,

いくつかの自己解凍型ZIPファイルには、偽のローカルファイルヘッダマーカーがあります。私は End of Central Directory レコードを見つけるために、ファイルを後方にスキャンするのが最善の方法だと思います。 EOCD レコードに中央ディレクトリのオフセットがあり、 CD に最初のローカルファイルヘッダのオフセットが含まれています。 ローカルファイルヘッダー ZipInputStream の最初のバイトから読み込みを開始するとうまく動作します。

明らかに、以下のコードは最速の解決策ではありません。大きなファイルを処理する場合は、何らかのバッファリングを実装するか、メモリマップファイルを使用する必要があります。

import org.apache.commons.io.EndianUtils;
...

public class ZipHandler {
    private static final byte[] EOCD_MARKER = { 0x06, 0x05, 0x4b, 0x50 };

    public InputStream openExecutableZipFile(Path zipFilePath) throws IOException {
        try (RandomAccessFile raf = new RandomAccessFile(zipFilePath.toFile(), "r")) {
            long position = raf.length() - 1;
            int markerIndex = 0;
            byte[] buffer = new byte[4];
            while (position > EOCD_MARKER.length) {
                raf.seek(position);
                raf.read(buffer, 0 ,1);
                if (buffer[0] == EOCD_MARKER[markerIndex]) {
                    markerIndex++;
                } else {
                    markerIndex = 0;
                }
                if (markerIndex == EOCD_MARKER.length) {
                    raf.skipBytes(15);
                    raf.read(buffer, 0, 4);
                    int centralDirectoryOffset = EndianUtils.readSwappedInteger(buffer, 0);
                    raf.seek(centralDirectoryOffset);
                    raf.skipBytes(42);
                    raf.read(buffer, 0, 4);
                    int localFileHeaderOffset = EndianUtils.readSwappedInteger(buffer, 0);
                    return new SkippingInputStream(Files.newInputStream(zipFilePath), localFileHeaderOffset);
                }
                position--;
            }
            throw new IOException("No EOCD marker found");
        }
    }
}

public class SkippingInputStream extends FilterInputStream {
    private int bytesToSkip;
    private int bytesAlreadySkipped;

    public SkippingInputStream(InputStream inputStream, int bytesToSkip) {
        super(inputStream);
        this.bytesToSkip = bytesToSkip;
        this.bytesAlreadySkipped = 0;
    }

    @Override
    public int read() throws IOException {
        while (bytesAlreadySkipped < bytesToSkip) {
            int c = super.read();
            if (c == -1) {
                return -1;
            }
            bytesAlreadySkipped++;
        }
        return super.read();
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        if (bytesAlreadySkipped == bytesToSkip) {
            return super.read(b, off, len);
        }
        int count = 0;
        while (count < len) {
            int c = read();
            if (c == -1) {
                break;
            }
            b[count++] = (byte) c;
        }
        return count;
    }
}
1
追加された