7z 自解壓讀取 config.txt 配置的程式碼實現
- 2020 年 1 月 4 日
- 筆記
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; }