专栏首页jiajia_deng7z 自解压读取 config.txt 配置的代码实现

7z 自解压读取 config.txt 配置的代码实现

7z 自解压功能,实际是将三个文件连接在一起,第一个文件是 7z 的自解压模块(实际是一个通用的包含了界面界面的应用程序)+ config.txt(配置文件)+ 实际要解压的 7z 压缩包文件。三个文件通过 Windows 的 copy 命令拼接在一起,你也可以自己实现代码,将三个文件拼接在一起,因为第一个文件的首地址 PE 结构不变,所以当程序运行时相当于运行了 7z 的自解压模块。他们的组成如下图:

图中可以看的出来,使用 copy /b 将三个文件连接在了一起,我们需要在自解压的模块程序 7z_sfx.exe 中实现读取查找 config.txt 文件的位置和内容,从而也就可以得到自解压文件的起始位置。这样在解压文件的时候将包装在我们程序中的自解压程序起始地址传递进去就可以了。

前提条件

首先要在代码中找到被追加进自己程序的 config.txt 文件内容,config.txt 必须要有一个标识来记录文件的开头和结束,这样我们才知道这个文件中间的内容,参考 7z 自解压模块的代码,其在 config.txt 头部和尾部分别设计了两个标识,如下所示:

;!@Install@!UTF-8!
Title="SOFTWARE v1.0.0.0"
BeginPrompt="Do you want to install SOFTWARE v1.0.0.0?"
RunProgram="setup.exe"
;!@InstallEnd@!

在程序中只要将程序一块一块的读取到内存,对比每一个字节如果存在 ;!@Install@!UTF-8! 就是 config 文件的开头,存在 ;!@InstallEnd@! 就是 config 文件的结尾。这样中间的内容也就确定了,文件结尾的位置就是 7z 压缩包文件的开头。

实现代码

代码实现起来要考虑的内容还是比较多的,我参考了 7z 的代码从头实现了一遍,对每一个变量都做了作用注释,因为 7z 官方的代码一个注释都没有,看起来很难懂,索性就参考他的思路一点一点重写了一遍。调用 FindSignature 方法就可以查找到 config.txt 中的内容了,用 strOutput 参数将内容传出。

程序编译完成后,使用 copy /b 程序名 + 带有标记的 config.txt 就可以测试出效果,自己再加上解压的代码你就可以实现一个属于自己的自解压模块了。

#include <iostream>
#include <fstream>
#include <windows.h>

static char kBeginSignature[] = { ';','!','@','I','n','s','t','a','l','l','@','!','U','T','F','-','8','!', 0 };
static char kEndSignature[] = { ';','!','@','I','n','s','t','a','l','l','E','n','d','@','!', 0 };

bool FindSignature(const std::string& strBeginSignature, const std::string& strEndSignature, std::string& strOutput)
{
    const size_t nFixedBufferSize = (1 << 12);
    char szApplication[MAX_PATH] = { 0 };
    GetModuleFileNameA(NULL, szApplication, MAX_PATH);

    FILE* hFile = NULL;
    fopen_s(&hFile, szApplication, "rb");

    // 标记是否找到头部
    bool bFoundBegin = false;

    // 记录需要跳过多少个字节(上一次读取长度不足的内容会被填充到当前 buffer 中)
    size_t nBytesPrev = 0;

    BYTE szBuffer[nFixedBufferSize] = { 0 };

    for (;;)
    {
        size_t nReadSize = nFixedBufferSize - nBytesPrev;
        size_t nProcessedSize = fread(szBuffer + nBytesPrev, 1, nReadSize, hFile);
        if (nProcessedSize == 0)
            return false;

        // 上一次读取剩余的字节 + 本次读取到的字节总数
        size_t nTotalSize = nBytesPrev + nProcessedSize;

        // 标记读取出来的内存块中已经对比的数据位置
        size_t nPos = 0;

        for (;;)
        {
            if (!bFoundBegin)
            {
                // 剩余长度不足头部内容,直接跳出
                if (nPos > nTotalSize - strBeginSignature.size())
                    break;

                // 标记已经找到头部,找到头部后将读取指针移动到头部关键字末尾
                if (memcmp(szBuffer + nPos, strBeginSignature.c_str(), strBeginSignature.size()) == 0)
                {
                    bFoundBegin = true;
                    nPos += strBeginSignature.size();
                }
                else
                {
                    nPos++;
                }
            }
            else
            {
                // 剩余长度不足尾部内容,直接跳出
                if (nPos > nTotalSize - strEndSignature.size())
                    break;

                // 如果找到末尾则直接返回
                if (memcmp(szBuffer + nPos, strEndSignature.c_str(), strEndSignature.size()) == 0)
                    return true;

                // 将不是末尾标记的数据追加给传出参数
                BYTE pByte = szBuffer[nPos];

                // 程序中常量字符串末尾是 0,但文件中不是 0,如果读到 0 证明是程序中的常量,而不是文件中的
                if (pByte == 0)
                {
                    bFoundBegin = false;
                    break;
                }
                strOutput += pByte;

                // 向后步进
                nPos++;
            }
        }

        // 记录下次需要跳过的字节数量
        nBytesPrev = nTotalSize - nPos;

        // 将不足以对比的剩余内容拷贝到 buffer 的首位,下次读取的新数据衔接在该数据后面
        memmove(szBuffer, szBuffer + nPos, nBytesPrev);
    }

    if (hFile)
    {
        fclose(hFile);
    }

    return false;
}

int main()
{
    std::string strOutput;
    if (FindSignature(kBeginSignature, kEndSignature, strOutput))
        std::cout << strOutput.c_str() << std::endl;
    else
        std::cout << "Not found..." << std::endl;

}

相关

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • VC 实现将程序从任务栏解锁或锁定到任务栏

    以前写过一些脚本调用 Shell Object 实现的解锁和锁定程序到任务栏的功能,这几天刚好用到,却不想用脚本实现,由于没有了解过 VC 如何操作 Shell...

    我与梦想有个约会
  • react-router 中 IndexRoute、IndexRedirect 的区别介绍

    本文内容引用了 react-router v3 的文档介绍来举例说明,内容并不重复,我们会以项目开发的角度来分析这两个功能的实际作用,更有助于理解和使用这两个功...

    我与梦想有个约会
  • 使用 CMake + Git Submodule 方式管理项目三方库

    在项目开发过程中,避免不了要使用一些开源的三方库,我参加过的一些团队有不同的管理三方库的方式。不同的方式都各有优缺点,我们先列举一下碰到过的管理方式,说一些他们...

    我与梦想有个约会
  • 对象生命周期管理设计

    这是一个让我相见恨晚的设计图,折腾了一圈,目前来看,能够满足我预设的绝大多数场景。

    jeanron100
  • 深度学习神经网络第①篇——感知器及其Python实现

    感知器是由美国计算机科学家罗森布拉特(F.Roseblatt)于1957年提出的。感知器可谓是最早的人工神经网络。单层感知器是一个具有一层神经元、采用阈值激活函...

    小小詹同学
  • 解决IDEA在软件里修改了内存后打不开的问题

    Happyjava
  • Python+flask+flask-email发送带附件的电子邮件

    运行下面的程序之前,需要使用pip install flask-mail安装电子邮件扩展包。 import os.path from flask import ...

    Python小屋屋主
  • 第3阶段——内核启动分析之内核编译试验(1)

    目标: 1.给内核打补丁 2.配置 3.编译 4.重启   1 内核打补丁 1.1 通过FTP将Windows里system目录下linux-2.6.22.6....

    张诺谦
  • Oracle应用实战六——函数+集合

    函数 字符函数 接收字符输入返回字符或者数值,dual是伪表 1. 把小写的字符转换成大小的字符 upper('smith') ? 2. 把大写字符变成小写字符...

    奋斗蒙
  • 8b/10b编码技术系列(一):Serdes、CDR、K码

    和大家分享一下关于8b/10b编码的知识点,如有什么错误之处或大家有什么额外的见解欢迎大家公众号后台留言!

    根究FPGA

扫码关注云+社区

领取腾讯云代金券