我曾在《【Delphi】如何在可执行文件中动态添加数据》讨论过如何通过Delphi程序给已经存在的EXE程序添加数据。当时使用的是对象TPayload对象,通过TPayloadFooter水印给EXE添加数据,使用起来是没有任何问题的。但是有一点小小的限制,那就是吸入数据和读出数据,使用的都是TBytes结构,这样受制于内存的大小等无法实现附加大数据,比如附加2G的数据。
而本文要讨论的就是不直接使用TBytes,而是使用TFileStream,这样就可以附加大数据,比如2G的数据。同时增加了对TPayloadFooter的校验,更加可靠。
当然,为了方便使用,再次提供了TBytes参数的函数,这也会受到内存大小的限制!
根据实际情况,我们设计4个函数,两两一对,分别支持文件类型和TBytes类型。
| 序号 | 函数名称 | 说明 |
| 1.1 | function AppendFileToExe(const F1FileName, F2FileName: string; out ErrMsg : string): Boolean; | 将文件F2FileName附加到文件F1FileName的末尾, 成功True,失败则False,同时Errmsg包含错误信息 |
| 1.2 | function ExtractFileFromExe(const SourceFileName, DestFileName: string; out ErrMsg : string): Boolean; | 对应上述函数,从文件SourceFileName尾部解析出文件DestFileName,解析的条件是根据水印 ExpectedWaterMark。成功True,失败则False |
| 2.1 | function AppendBytesToExe(const F1FileName: string; F2Bytes : TBytes; out ErrMsg : string): Boolean; | 将数据流F2Bytes附加到文件F1FileName的末尾, 成功True,失败则False,同时Errmsg包含错误信息 |
| 2.2 | function ExtractBytesFromExe(const SourceFileName: string; out F2Bytes : TBytes; out ErrMsg : string): Boolean; | 对应上述函数,从文件SourceFileName尾部解析出文件数据流F2Bytes,解析的条件是根据水印 ExpectedWaterMark。成功True,失败则False,同时Errmsg包含错误信息 |
一、函数源代码
1. AppendFileToExe
function AppendFileToExe(const F1FileName, F2FileName: string; out ErrMsg : string): Boolean;
var
F1Stream, F2Stream: TFileStream;
Footer: TPayloadFooter;
F1Size, F2Size: DWORD;
WaterMark : TGUID;
// 将指定内存块的每个字节累加返回(无符号字节求和,结果为 Int64)
function SumBytes(const Buf; BufSize: NativeInt): Int64;
var
p: PByte;
i: NativeInt;
begin
Result := 0;
p := PByte(@Buf);
for i := 0 to BufSize - 1 do
Result := Result + p[i];
end;
//计算Footer的校验和
function Make_Footer_Sum(Footer: TPayloadFooter): TPayloadFooter;
var
bsSum: Int64;
rnd: Cardinal;
begin
// 生成 32 位随机数(0 .. High(Cardinal))
// 使用 Delphi RTL 的 RandSeed / Random 或 Windows API CryptGenRandom 可替换以增加随机性
// 这里使用 Random32 的简单实现
rnd := Cardinal(Random(MaxInt)); // Random(MaxInt) 返回 0..MaxInt-1,MaxInt=2^31-1
// 为覆盖全 32 位范围,可以组合两次 Random:
rnd := (Cardinal(Random(MaxInt)) shl 16) xor Cardinal(Random(MaxInt));
// 计算 WaterMark(16 字节) + ExeSize (8 字节) + DataSize (8 字节) 的字节和
bsSum := 0;
bsSum := bsSum + SumBytes(Footer.WaterMark, SizeOf(TGUID));
bsSum := bsSum + SumBytes(Footer.ExeSize, SizeOf(Int64));
bsSum := bsSum + SumBytes(Footer.DataSize, SizeOf(Int64));
// 更新 Random 和 Sum 字段
Footer.Random := Int32(rnd);
// 乘法(注意溢出可能性,若启用了溢出检查需考虑)
Footer.Sum := bsSum + Int64(rnd);
Result := Footer;
end;
begin
Result := False;
F1Stream := nil;
F2Stream := nil;
try
// 检查文件是否存在
if not FileExists(F1FileName) then
begin
ErrMsg := 'A1_1: 目标EXE文件 ' + F1FileName + ' 不存在!';
Exit(False);
end;
if not FileExists(F2FileName) then
begin
ErrMsg := 'A1_2: 附加文件 ' + F2FileName + ' 不存在!';
Exit(False);
end;
// 生成唯一水印,目前使用唯一的值
WaterMark := cWaterMarkGUID; // CreateGUID(WaterMark);
// 打开F1文件获取原始大小
F1Stream := TFileStream.Create(F1FileName, fmOpenRead or fmShareDenyWrite);
try
F1Size := F1Stream.Size;
// 检查文件大小是否超过LongInt的限制
// if F1Size > MaxInt then
// raise Exception.Create('文件大小超过限制(2GB)');
finally
F1Stream.Free;
F1Stream := nil;
end;
// 打开F2文件获取大小和内容
F2Stream := TFileStream.Create(F2FileName, fmOpenRead or fmShareDenyWrite);
try
F2Size := F2Stream.Size;
// 检查文件大小是否超过LongInt的限制
if F2Size > MaxInt then
begin
ErrMsg := 'A1_3: 附加数据文件太大,超过限制(2GB)';
Exit(False);
end;
// 准备Footer结构
Footer.WaterMark := WaterMark;
Footer.ExeSize := F1Size;
Footer.DataSize := F2Size;
//计算Sum 校验码
Footer := Make_Footer_Sum(Footer);
// 以追加模式打开F1文件
F1Stream := TFileStream.Create(F1FileName, fmOpenReadWrite or fmShareDenyWrite);
try
// 移动到文件末尾
F1Stream.Position := F1Stream.Size;
// 写入F2文件内容
F1Stream.CopyFrom(F2Stream, 0);
// 写入Footer结构
F1Stream.WriteBuffer(Footer, SizeOf(TPayloadFooter));
ErrMsg := 'A1_4: 附加数据成功!';
Result := True;
finally
F1Stream.Free;
end;
finally
F2Stream.Free;
end;
except
on E: Exception do
begin
// 在实际应用中,这里可以添加日志记录
ErrMsg := 'A1_5: ' + E.Message;
Result := False;
end;
end;
end;
2. ExtractFileFromExe
function ExtractFileFromExe(const SourceFileName, DestFileName: string; out ErrMsg : string): Boolean;
var
SourceStream, DestStream: TFileStream;
Footer: TPayloadFooter;
FileSize: Int64;
FooterPos: Int64;
DataPos: Int64;
// 将指定内存块的每个字节累加返回(无符号字节求和,结果为 Int64)
function SumBytes(const Buf; BufSize: NativeInt): Int64;
var
p: PByte;
i: NativeInt;
begin
Result := 0;
p := PByte(@Buf);
for i := 0 to BufSize - 1 do
Result := Result + p[i];
end;
function Check_Footer_Sum(const Footer: TPayloadFooter): Boolean;
var
bsSum: Int64;
expected: Int64;
rnd: Cardinal;
begin
// 将 Random 视为无符号以与 Make_Footer_Sum 中的处理一致
rnd := Cardinal(Footer.Random);
// 计算 WaterMark(16) + ExeSize(8) + DataSize(8) 的字节和
bsSum := 0;
bsSum := bsSum + SumBytes(Footer.WaterMark, SizeOf(TGUID));
bsSum := bsSum + SumBytes(Footer.ExeSize, SizeOf(Int64));
bsSum := bsSum + SumBytes(Footer.DataSize, SizeOf(Int64));
// 计算期望的 Sum(注意:可能溢出,按 Int64 截断/表现)
expected := bsSum + Int64(rnd);
// 比较期望值与 Footer 中存储的 Sum
Result := (expected = Footer.Sum);
end;
begin
Result := False;
SourceStream := nil;
DestStream := nil;
try
// 检查源文件是否存在
if not FileExists(SourceFileName) then
begin
ErrMsg := 'A2_1: 源文件 ' + SourceFileName + ' 不存在';
Exit(False);
end;
// 打开源文件
SourceStream := TFileStream.Create(SourceFileName, fmOpenRead or fmShareDenyWrite);
try
FileSize := SourceStream.Size;
// 检查文件是否足够大以包含Footer
if FileSize < SizeOf(TPayloadFooter) then
begin
ErrMsg := 'A2_2: 文件太小,不包含有效的PayloadFooter';
Exit(False);
end;
// 读取Footer结构
FooterPos := FileSize - SizeOf(TPayloadFooter);
SourceStream.Position := FooterPos;
SourceStream.ReadBuffer(Footer, SizeOf(TPayloadFooter));
//首先验证Sum校验码是否正确
if not Check_Footer_Sum(Footer) then
begin
ErrMsg := 'A2_3: Footer 校验失败(可能未包含附加数据)!';
Exit(False);
end;
// 验证水印
if not IsEqualGUID(Footer.WaterMark, cWaterMarkGUID) then
begin
ErrMsg := 'A2_4: 水印验证失败,文件可能已损坏或不是目标文件';
Exit(False);
end;
// 验证文件大小是否匹配
if (Int64(Footer.ExeSize) + Int64(Footer.DataSize) + SizeOf(TPayloadFooter)) <> FileSize then
begin
ErrMsg := 'A2_5: 文件大小不匹配,文件可能已损坏!';
Exit(False);
end;
// 验证数据大小是否有效
if Footer.DataSize <= 0 then
begin
ErrMsg := 'A2_6: 无效的附加数据大小(0)';
Exit(False);
end;
// 计算数据起始位置
DataPos := Footer.ExeSize;
// 验证数据起始位置是否有效
if DataPos < 0 then
begin
ErrMsg := 'A2_7: 无效的附加数据起始位置';
Exit(False);
end;
if (DataPos + Footer.DataSize) > FileSize then
begin
ErrMsg := 'A2_8: 数据超出文件范围';
Exit(False);
end;
// 创建目标文件并写入数据
DestStream := TFileStream.Create(DestFileName, fmCreate or fmShareDenyWrite);
try
SourceStream.Position := DataPos;
DestStream.CopyFrom(SourceStream, Footer.DataSize);
ErrMsg := 'A2_9: 附加数据解包成功';
Result := True;
finally
DestStream.Free;
end;
finally
SourceStream.Free;
end;
except
on E: Exception do
begin
// 在实际应用中,这里可以添加日志记录
Result := False;
ErrMsg := E.Message;
// 如果目标文件已创建但提取失败,删除它
if FileExists(DestFileName) then
DeleteFile(PChar(DestFileName));
end;
end;
end;

541

被折叠的 条评论
为什么被折叠?



