月度归档:2019年04月

彻底使用 Cmder 替换掉 Windows 系统默认 Cmd.exe

安装 Cmder 以后,默认的配置中是没有替换掉 Cmd.exe 的,一直以来我认为是没有这种方法可以替换的,包括什么镜像劫持等方式。但是最近看到一篇文章可以在注册表 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths 下创建一个 cmd.exe 子项,然后在 Default 属性中设置要替换的程序。这样设置虽然可以替换到运行中输入 cmd.exe。但是无法替换直接运行批处理文件打开的 cmd.exe。即使你修改了 batfile 的关联程序,也没有特别好的方法去使用 Cmder 来启动一个批处理。

继续阅读

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;

}

CEF 修改请求 header 与单独处理 header 中的 referer

有些时候利用 CEF 内嵌的页面加载某些资源的时候需要附带一些头信息,比如里面的图片需要携带一些校验和信息才能正常访问的,这个时候就需要在发起请求前对 HTTP Request 的 Header 部分进行修改。CEF 提供了两个接口用于读写 Request Header,分别是 CefLifeSpanHandler::OnAfterCreatedCefRequestHandler::OnBeforeResourceLoad 方法。两个都是虚函数,需要继承并重写两个方法来实现对 Request Header 的读取和修改。

继续阅读

Qt QSS 属性选择器使用详解

Qt 的属性选择器是其独有的,非常类似 CSS 的类选择器,但是由于 CSS 的类选择器可以设置多个,所以一个标签只要设置多个的 class 就可以实现不同的效果了甚至重叠效果。但是 Qt 的类选择器没有那么强大,他不能给控件设置多个类标识。所以就有了属性选择器这么个东西。

继续阅读