码迷,mamicode.com
首页 > Windows程序 > 详细

使用zlib解压.apk/.zip文件(Windows&Ubuntu)

时间:2016-05-06 13:09:05      阅读:460      评论:0      收藏:0      [点我收藏+]

标签:

前言

前面讲过,解压出apk文件的内容是进行apk分析的第一步,而.apk文件其实就是.zip文件。也就是说首先要实现zip文件的解压缩。本文将分别介绍在Windows和Ubuntu下如何使用zlib这一开源库对zip文件进行解压。

ZLIB

zlib is designed to be a free, general-purpose, legally unencumbered – that is, not covered by any patents – lossless data-compression library for use on virtually any computer hardware and operating system. The zlib data format is itself portable across platforms.

zlib是一个C语言编写的、开源的、几乎在所有的计算机和操作系统上都使用的数据压缩库,更多的介绍请查看zlib官方网站;
简而言之,zlib其实是一种数据压缩算法,zip文件压缩只是这种算法的应用之一。
关于zlib压缩算法参考这篇文章:http://blog.jobbole.com/76676/
在zlib的源码中,有一个\contrib\minizip文件夹,这个文件夹中封装了zlib算法,使得其可以用来解压和压缩zip格式的文件。

下载ZLIB

zlib目前的版本是1.2.8,要下载的内容包括源码和编译好的dll文件(也可以自己使用源码编译)。
zlib source code:http://zlib.net/zlib-1.2.8.tar.gz
zlib compiled DLL:http://zlib.net/zlib128-dll.zip
下载完成后将这两个文件解压。

zlib算法和zip文件

zlib算法

网上有很多关于zlib的文章描述的都是如何使用zlib进行压缩和解压字节流,也就是使用

compress (Bytef *dest,   uLongf *destLen, const Bytef *source, uLong sourceLen)

来对字节流进行压缩,使用

 uncompress (Bytef *dest,   uLongf *destLen,const Bytef *source, uLong sourceLen)

来对字节流进行解压缩。
这两个函数的操作对象就是读入内存的字节流。下面给出一个使用这两个函数的例子(暂时不用关心如何正确配置zlib库使得程序顺利编译)

#include <iostream>
#include <zlib.h>
#include <cstring>
using namespace std;
int main()
{
    char str[11] = "HelloWorld";
    Byte *Source ;
    Byte *Destination;
    unsigned long DestLen ;
    unsigned long SourLen = strlen(str)+1;
    DestLen = compressBound(SourLen);
    Source = new Byte[SourLen];
    Destination = new Byte[DestLen];
    Source = (Byte *)str;
    int err = compress(Destination, &DestLen, Source, SourLen);
    if (err == 0)
    {
        cout <<"压缩完成"<< endl;
        cout << "原字符串:" << Source << endl;
        cout << "压缩后字符串:" << Destination << endl;
        cout << "压缩后字符串长度:" << DestLen << endl;
    }
    else
    {
        cout << "压缩失败" << endl;
    }
    Byte *uncompressSource;
    Byte *uncompressDest;
    unsigned long uncompressSourceLen = DestLen;
    unsigned long uncompressDestLen = SourLen;
    uncompressSource = Destination;
    uncompressDest = new Byte[uncompressDestLen];
    err = uncompress(uncompressDest, &uncompressDestLen, uncompressSource, uncompressSourceLen);
    if (err == 0)
    {
        cout << "解压完成" << endl;
        cout << "解压前字符串:" << uncompressSource << endl;
        cout << "解压后字符串:" << uncompressDest << endl;
        cout << "解压后后字符串长度:" << uncompressDestLen << endl;
    }
    else
    {
        cout << "解压失败" << endl;
    }
    system("pause");
    return 0;
}

运行结果:
技术分享
至于为什么压缩后的字节长度比压缩前还长,那是因为我们压缩的字节不够长(11个Byte),如果有足够长的字节,那么压缩效果就可以看出来了。
当然,你也可以把“HelloWorld”这个字符串替换成从外部文件中读取的一段字节流。
但是这个结果并不是我们想要的,我们要做的事解压zip文件。

zip文件

前面提到,zip文件压缩和解压只是对zlib算法的一种应用。事实上,在前面的对字节流的压缩完成后,如果直接将压缩后的数据写入到一个.zip文件中,使用一般的zip解压缩软件是不能打开的。因为zip文件有一定的文件结构。关于zip文件的结构网上有很多文章,就不再赘述了。
要使用zlib库对zip文件进行压缩和解压,就需要我们在zlib库的基础上再做额外的工作,使得其符合zip文件结构。感谢zlib的开发人员,这个工作他们已经做好了。
在zlib的源码中,找到\contrib\minizip文件夹,这个文件夹中有两个头文件zip.h和unzip.h,这就是对zlib库的封装。还有两个例子minizip.c和miniunz.c,描述了压缩和解压zip文件的基本操作。

在VS2013中实现解压zip文件

配置

  1. 下载前面zlib的源码和编译好的dll文件,解压。
  2. 使用vs2013新建一个C++空项目
  3. 将zlib源码中的minizip文件夹中的
    • ioapi.h
    • ioapi.c
    • unzip.h
    • unzip.c
      复制到项目目录中:
      技术分享
  4. 将zlib的dll文件中/lib文件夹的zdll.lib文件复制到项目目录中
  5. 在VS2013中新建一个源文件
  6. 将ioapi.c和unzip.c添加到项目中
    技术分享
  7. 打开项目属性
    技术分享
  8. 将配置属性->链接器->常规->附加库目录修改为刚才添加zdll.lib文件所在的目录
    技术分享
  9. 在配置属性->链接器->输入->附加依赖项中,添加zdll.lib
    技术分享
  10. 将zlib的DLL文件中zlib1.dll复制到项目调试生成的.exe文件所在的文件夹。

代码

头文件

#include <iostream>
#include <zlib.h>
#include "unzip.h"
#include <cstring>
#include <fstream>
#include <direct.h>
using namespace std;

direct.h头文件用来创建目录
C++创建目录的操作在Windows和Unix下是不同的。
在Windows下,要

#include<direct.h>

使用

int _mkdir(_In_z_ const char * _Path)

函数。
在Unix下,要

#include<sys/stat.h>
#include<sys/types.h>

使用

int mkdir(const char *pathname, mode_t mode)

函数。
参考这两篇文章:

API分析

涉及到的minizip库中的解压缩函数有:

unzFile unzOpen64(const char *path);
int unzClose(unzFile file);
int unzGetGlobalInfo64(unzFile file, unz_global_info *pglobal_info);
int unzGoToNextFile(unzFile file);
int unzGetCurrentFileInfo64(unzFile file, unz_file_info *pfile_info, char *szFileName, uLong fileNameBufferSize, void *extraField, uLong extraFieldBufferSize, char *szComment, uLong commentBufferSize);
int unzOpenCurrentFile(unzFile file);
int unzCloseCurrentFile(unzFile file);
int unzReadCurrentFile(unzFile file, voidp buf, unsigned len);

一个解压缩过程包括:
1. 使用unzOpen64函数打开一个zip文件;
2. 使用unzGetGlobalInfo64函数读取zip文件的全局信息,保存在一个unz_global_info64类型的结构体中;
3. 使用unz_global_info64结构体中的number_entry变量循环读取zip文件中的文件,并进行读取、解压、写入、保存等操作
4. 在unzFile结构中,有一个指针指向当前文件,默认指在第一个文件。操作完一个文件后,使用unzCloseCurrentFile函数关闭这个文件,再使用unzGoToNextFile函数使得指针指在下一个文件上,再对该文件进行操作;
5. 解压完整个zip文件后,使用unzClose函数释放资源。

编写main函数

int main()
{
    unzFile zfile;//定义一个unzFile类型的结构体zfile
    //定义zip文件的路径,可以使用相对路径
    char filepath[]="extract/test.zip";
    //调用unzOpen64()函数打开zip文件
    zfile = unzOpen64(filepath);
    if (zfile == NULL)
    {
        cout << filepath << "[INFO] 打开压缩文件失败" << endl;
        return -1;
    }
    else
    {
        cout << "[INFO] 成功打开压缩文件" << endl;
    }
    unz_global_info64 zGlobalInfo;
    //unz_global_info64是一个结构体
    //其中最重要的是number_entry成员
    //这个变量表示了压缩文件中的所有文件数目(包括文件夹、
    //文件、以及子文件夹和子文件夹中的文件)
    //我们用这个变量来循环读取zip文件中的所有文件
    if (UNZ_OK != unzGetGlobalInfo64(zfile, &zGlobalInfo))
    //使用unzGetGlobalInfo64函数获取zip文件全局信息
    {
        cout << "[ERROR] 获取压缩文件全局信息失败" << endl;
        return -1;
    }
    //循环读取zip包中的文件,
    //在extract_currentfile函数中
    //进行文件解压、创建、写入、保存等操作
    for (int i = 0; i < zGlobalInfo.number_entry; i++)
    {
        //extract_currentfile函数的第二个参数指将文件解压到哪里
        //这里使用extract/,表示将其解压到运行目录下的extract文件夹中
        int err = extract_currentfile(zfile, "extract/");
        //关闭当前文件
        unzCloseCurrentFile(zfile);
        //使指针指向下一个文件
        unzGoToNextFile(zfile);
    }
    //关闭压缩文件
    unzClose(zfile);
    system("pause");
    return 0;
}

编写extract_currentfile函数

输入参数:
1. unzFile zfile——要解压的unzFile类型的变量
2. char * extractdirectory——解压的目标文件夹路径即解压到哪里
Return:
int

这个函数的流程如下:
1. 使用

unzGetCurrentFileInfo64(zfile, &zFileInfo, fileName_WithPath, fileName_BufSize, NULL, 0, NULL, 0)

函数获取当前zfile文件中指针指向的文件的文件信息,保存在unz_file_info64类型的结构体zFileInfo变量中,获取当前文件相对于zip文件的相对路径和文件名,保存在字符串fileName_WithPath中;
2. 修改fileName_WithPath变量,使得其包含解压的目标文件夹
3. 判断当前文件是目录还是文件,如果是目录则创建目录,如果是文件则解压文件
4. 如果是文件,则使用 unzOpenCurrentFile函数打开当前文件
5. 使用fstream库以二进制流的形式创建当前文件
6. 使用unzReadCurrentFile读取当前文件内容到一个字符串中
7. 将字符串写入创建好的文件

int extract_currentfile(unzFile zfile,char * extractdirectory)
{
    unsigned int fileName_BufSize = 512;
    char *fileName_WithPath=new char[fileName_BufSize];
    char *p,*fileName_WithoutPath;
    unz_file_info64 zFileInfo;
    p = fileName_WithoutPath = fileName_WithPath;
    if (UNZ_OK != unzGetCurrentFileInfo64(zfile,
     &zFileInfo, fileName_WithPath,
      fileName_BufSize, NULL, 0, NULL, 0))
    {
        cout << "[ERROR] 获取当前文件信息失败" << endl;
        return -1;
    }
    char *temp = new char[fileName_BufSize];
    //修改fileName_WithPath,使得extractdirectory加在其前面
    strcpy_s(temp, 512, extractdirectory);
    strcat_s(temp, 512, fileName_WithPath);
    fileName_WithPath = temp;
    //判断当前文件是目录还是文件
    while ((*p) != ‘\0‘)
    {
        if (((*p) == ‘/‘) || ((*p) == ‘\\‘))
            fileName_WithoutPath = p + 1;
        p++;
    }
    if (*fileName_WithoutPath == ‘\0‘)
    {
        cout << "[INFO] " << "成功读取当前目录:" << fileName_WithPath << endl;
        cout << "[INFO] " << "开始创建目录:" << fileName_WithPath << endl;
        //创建目录
        int err = _mkdir(fileName_WithPath);
        if (err != 0)
            cout << "[ERROR] " << "创建目录" 
            << fileName_WithPath << "失败" << endl;
        else
            cout << "[INFO] " << "成功创建目录" 
            << fileName_WithPath << endl;
    }
    else
    {
        cout << "[INFO] " << "成功读取当前文件:" 
        << fileName_WithoutPath << endl;
        cout << "[INFO] " << "开始解压当前文件:" 
        << fileName_WithoutPath << endl;
        //打开当前文件
        if (UNZ_OK != unzOpenCurrentFile(zfile))
        {
            //错误处理信息  
            cout <<"[ERROR] "<< "打开当前文件" << 
            fileName_WithoutPath << "失败!" << endl;
        }
        //定义一个fstream对象,用来写入文件
        fstream file;
        file.open(fileName_WithPath, ios_base::out | ios_base::binary);
        ZPOS64_T fileLength = zFileInfo.uncompressed_size;
        //定义一个字符串变量fileData,读取到的文件内容将保存在该变量中
        char *fileData = new char[fileLength];
        //解压缩文件  
        ZPOS64_T err = unzReadCurrentFile(zfile, (voidp)fileData, fileLength);
        if (err<0)
            cout << "[ERROR] " << "解压当前文件" 
            << fileName_WithoutPath << "失败!" << endl;
        else
            cout << "[INFO] " << "解压当前文件"  
            << fileName_WithoutPath << "成功!" << endl;
        file.write(fileData, fileLength);
        file.close();
        free(fileData);
    }
    return 0;
}

运行前

需要在运行目录下创建一个extract文件夹,将要解压的文件复制到该文件夹下,并对代码中的压缩文件夹路径进行修改
技术分享
技术分享

运行

技术分享
在/extract文件夹中查看解压好的文件:
技术分享

在Ubuntu中实现解压zip文件

配置

  1. 安装gcc&g++
  2. 下载zlib源码并解压
  3. 进入zlib源码所在文件夹,打开终端
  4. 运行./configure
    技术分享
  5. 运行make
    技术分享
  6. 运行sudo make install
    技术分享
  7. 关于安装zlib的更多信息,参见:http://www.linuxidc.com/Linux/2012-06/61982.htm

代码

代码和windows下唯一的区别在于前面提到的创建文件夹要包含的头文件和使用的函数不同。
创建一个zlibtest.cpp文件,输入下列代码

#include <iostream>
#include <zlib.h>
#include "unzip.h"
#include <cstring>
#include <fstream>
#include <sys/stat.h>
#include <sys/types.h>
using namespace std;
int extract_currentfile(unzFile zfile,char *extractdirectory)
{
unsigned int fileName_BufSize=512;
char *fileName_WithPath=new char[fileName_BufSize];
char *p,*fileName_WithoutPath;
unz_file_info64 zFileInfo;
p = fileName_WithoutPath = fileName_WithPath;
if (UNZ_OK != unzGetCurrentFileInfo64(zfile, 
    &zFileInfo, fileName_WithPath, fileName_BufSize, NULL, 0, NULL, 0))
{
cout << "[ERROR] 获取当前文件信息失败" << endl;
return -1;
}
char *temp = new char[fileName_BufSize];
strcpy(temp,extractdirectory);
strcat(temp,fileName_WithPath);
fileName_WithPath = temp;
while ((*p) != ‘\0‘)
{
if (((*p) == ‘/‘) || ((*p) == ‘\\‘))
fileName_WithoutPath = p + 1;
p++;
}
if (*fileName_WithoutPath == ‘\0‘)
{
cout << "[INFO] " << "成功读取当前目录:" << fileName_WithPath << endl;
cout << "[INFO] " << "开始创建目录:" << fileName_WithPath << endl;
int err = mkdir(fileName_WithPath,S_IRWXU|S_IRWXG|S_IROTH);
if (err != 0)
cout << "[ERROR] " << "创建目录" << fileName_WithPath << "失败" << endl;
else
cout << "[INFO] " << "成功创建目录" << fileName_WithPath << endl;
}
else
{
cout << "[INFO] " << "成功读取当前文件:" << fileName_WithoutPath << endl;
cout << "[INFO] " << "开始解压当前文件:" << fileName_WithoutPath << endl;
if (UNZ_OK != unzOpenCurrentFile(zfile))
{
//错误处理信息  
cout <<"[ERROR] "<< "打开当前文件" << fileName_WithoutPath << "失败!" << endl;
}
fstream file;
file.open(fileName_WithPath, ios_base::out | ios_base::binary);
ZPOS64_T fileLength = zFileInfo.uncompressed_size;
char *fileData = new char[fileLength];
//解压缩文件  
ZPOS64_T err = unzReadCurrentFile(zfile, (voidp)fileData, fileLength);
if (err<0)
cout << "[ERROR] " << "解压当前文件" << fileName_WithoutPath << "失败!" << endl;
else
cout << "[INFO] " << "解压当前文件" << fileName_WithoutPath << "成功!" << endl;
file.write(fileData, fileLength);
file.close();
free(fileData);
}
return 0;
}
int main()
{
unzFile zfile;
char filepath[]="./extract/test.zip";
zfile=unzOpen64(filepath);
if(zfile==NULL)
{
cout<<"[ERROR] 文件不存在"<<endl;
return -1;
}
else
{
cout << "[INFO] 成功打开压缩文件" << endl;
}
unz_global_info64 zGlobalInfo;
if (UNZ_OK != unzGetGlobalInfo64(zfile, &zGlobalInfo))
{
cout << "[ERROR] 获取压缩文件全局信息失败" << endl;
return -1;
}
for (int i = 0; i < zGlobalInfo.number_entry; i++)
{
int err = extract_currentfile(zfile, "extract/");
unzCloseCurrentFile(zfile);
unzGoToNextFile(zfile);
}
return 0;
}

编译

  1. 将zlib源码中/contrib/minizip文件夹下的文件:
    • ioapi.c
    • ioapi.h
    • unzip.c
    • unzip.h
      复制到zlibtest.cpp所在的文件夹下
  2. 由于ioapi.c和unzip.c都是C语言文件,不能直接和编写的zlibtest.cpp文件一起编译。所以首先编译出它们的.o目标文件
  3. 在项目所在文件夹中打开终端
  4. 运行gcc ./ioapi.c -c得到ioapi.o文件
  5. 运行gcc ./unzip.c -c得到unzip,o文件
  6. 运行
g++ ./ioapi.o ./unzip.o ./zlibtest.cpp -o zlibtest -L /usr/local/lib/libz.a /usr/local/lib/libz.so

?????得到zlibtest可执行文件
?????7. 在zlibtest.cpp所在文件夹中建立一个extract文件夹,将要解压的test.zip复制到里面
?????8. 运行./zlibtest,zip文件就会解压到extract文件中
技术分享

凡事多问为什么

  1. 首先,ioapi,c和unzip.c必须和zlibtest.cpp联合编译才能成功,因为解压的函数都是在这两个文件中实现的
  2. 在上面的第6步的命令中:
g++ ./ioapi.o ./unzip.o ./zlibtest.cpp -o zlibtest -L /usr/local/lib/libz.a /usr/local/lib/libz.so

-L参数后面链接了一个静态库libz.a,一个动态库libz.so
我是怎么知道要链接这两个库的呢(不链接会报错)?我是天才
查看刚才安装zlib的过程就可以看到,在sudo make install这一步,
技术分享
将一些库文件复制到了/usr/local/lib文件夹中,其中就包含了两个文件:libz.a和libz.so.1.2.8,但是在编译命令中我使用了libz.so而并没有使用libz.so.1.2.8.,为什么编译还是通过了呢?
那么我改用libz.so.1.2.8来编译,输入

g++ ./ioapi.o ./unzip.o ./zlibtest.cpp -o zlibtest -L /usr/local/lib/libz.a /usr/local/lib/libz.so.1.2.8

来编译,还是通过了。这就很奇怪了,libz.so是从哪里来的?
首先找到libz.so所在的文件夹也就是/usr/local/lib下:
技术分享
这里竟然有三个文件:libz.so,libz.so.1,libz.so.1.2.8!打开它们的属性,这三个文件竟然是同一时间创建的!而且具有相同的文件大小!
技术分享
这就表示,在创建libz.so.1.2.8的时候,其它两个文件就一起创建了。但是在sudo make install这一步并没有出现创建其它两个文件的命令,只有将libz.so.1.2.8复制到/usr/local/lib下的命令。
那么很可能就在前一步也就是运行make命令的时候创建的。
果然在make这一条命令运行的终端输出中发现了这个:
技术分享
这两条命令:
ln -s libz.so.1.2.8 libz.so
ln -s libz.so.1.2.8 libz.so.1
就是创建libz.so和libz.so.1的命令。ln的含义就是创建一个链接,可以理解为快捷方式。
但是为什么要创建这两个链接呢?
在gcc的命令参数中,-l和-L是用来链接库文件的,关于这两个参数的用法,参见文章:
http://www.cnblogs.com/benio/archive/2010/10/25/1860394.html
那么,我就可以使用-lz来替代-L参数了,如下:

g++ ./ioapi.o ./unzip.o ./zlibtest.cpp -o zlibtest -lz

技术分享
编译成功。
知道了-l的用法,创建libz.so.1.2.8的链接libz.so和libz.so.1的原因就很清楚了,就是为了-lz命令的使用。

结语

既然能够用gcc编译成功,那么在linux下使用QtCreator编写解压的程序就很简单了,因为QtCreator用的也是gcc嘛。
至于如何压缩文件,基本操作也可以在网上找到,重点就在如何配置编译环境。

使用zlib解压.apk/.zip文件(Windows&Ubuntu)

标签:

原文地址:http://blog.csdn.net/leehdsniper/article/details/51321501

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!