【Delphi】再谈给EXE文件动态添加数据(附加大数据 2G)
我曾在《【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;
varF1Stream, F2Stream: TFileStream;Footer: TPayloadFooter;F1Size, F2Size: DWORD;WaterMark : TGUID;// 将指定内存块的每个字节累加返回(无符号字节求和,结果为 Int64)function SumBytes(const Buf; BufSize: NativeInt): Int64;varp: PByte;i: NativeInt;beginResult := 0;p := PByte(@Buf);for i := 0 to BufSize - 1 doResult := Result + p[i];end;//计算Footer的校验和function Make_Footer_Sum(Footer: TPayloadFooter): TPayloadFooter;varbsSum: 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;beginResult := False;F1Stream := nil;F2Stream := nil;try// 检查文件是否存在if not FileExists(F1FileName) thenbeginErrMsg := 'A1_1: 目标EXE文件 ' + F1FileName + ' 不存在!';Exit(False);end;if not FileExists(F2FileName) thenbeginErrMsg := 'A1_2: 附加文件 ' + F2FileName + ' 不存在!';Exit(False);end;// 生成唯一水印,目前使用唯一的值WaterMark := cWaterMarkGUID; // CreateGUID(WaterMark);// 打开F1文件获取原始大小F1Stream := TFileStream.Create(F1FileName, fmOpenRead or fmShareDenyWrite);tryF1Size := F1Stream.Size;// 检查文件大小是否超过LongInt的限制// if F1Size > MaxInt then// raise Exception.Create('文件大小超过限制(2GB)');finallyF1Stream.Free;F1Stream := nil;end;// 打开F2文件获取大小和内容F2Stream := TFileStream.Create(F2FileName, fmOpenRead or fmShareDenyWrite);tryF2Size := F2Stream.Size;// 检查文件大小是否超过LongInt的限制if F2Size > MaxInt thenbeginErrMsg := '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;finallyF1Stream.Free;end;finallyF2Stream.Free;end;excepton E: Exception dobegin// 在实际应用中,这里可以添加日志记录ErrMsg := 'A1_5: ' + E.Message;Result := False;end;end;
end;
2. ExtractFileFromExe
function ExtractFileFromExe(const SourceFileName, DestFileName: string; out ErrMsg : string): Boolean;
varSourceStream, DestStream: TFileStream;Footer: TPayloadFooter;FileSize: Int64;FooterPos: Int64;DataPos: Int64;// 将指定内存块的每个字节累加返回(无符号字节求和,结果为 Int64)function SumBytes(const Buf; BufSize: NativeInt): Int64;varp: PByte;i: NativeInt;beginResult := 0;p := PByte(@Buf);for i := 0 to BufSize - 1 doResult := Result + p[i];end;function Check_Footer_Sum(const Footer: TPayloadFooter): Boolean;varbsSum: 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 中存储的 SumResult := (expected = Footer.Sum);end;beginResult := False;SourceStream := nil;DestStream := nil;try// 检查源文件是否存在if not FileExists(SourceFileName) thenbeginErrMsg := 'A2_1: 源文件 ' + SourceFileName + ' 不存在';Exit(False);end;// 打开源文件SourceStream := TFileStream.Create(SourceFileName, fmOpenRead or fmShareDenyWrite);tryFileSize := SourceStream.Size;// 检查文件是否足够大以包含Footerif FileSize < SizeOf(TPayloadFooter) thenbeginErrMsg := '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) thenbeginErrMsg := 'A2_3: Footer 校验失败(可能未包含附加数据)!';Exit(False);end;// 验证水印if not IsEqualGUID(Footer.WaterMark, cWaterMarkGUID) thenbeginErrMsg := 'A2_4: 水印验证失败,文件可能已损坏或不是目标文件';Exit(False);end;// 验证文件大小是否匹配if (Int64(Footer.ExeSize) + Int64(Footer.DataSize) + SizeOf(TPayloadFooter)) <> FileSize thenbeginErrMsg := 'A2_5: 文件大小不匹配,文件可能已损坏!';Exit(False);end;// 验证数据大小是否有效if Footer.DataSize <= 0 thenbeginErrMsg := 'A2_6: 无效的附加数据大小(0)';Exit(False);end;// 计算数据起始位置DataPos := Footer.ExeSize;// 验证数据起始位置是否有效if DataPos < 0 thenbeginErrMsg := 'A2_7: 无效的附加数据起始位置';Exit(False);end;if (DataPos + Footer.DataSize) > FileSize thenbeginErrMsg := 'A2_8: 数据超出文件范围';Exit(False);end;// 创建目标文件并写入数据DestStream := TFileStream.Create(DestFileName, fmCreate or fmShareDenyWrite);trySourceStream.Position := DataPos;DestStream.CopyFrom(SourceStream, Footer.DataSize);ErrMsg := 'A2_9: 附加数据解包成功';Result := True;finallyDestStream.Free;end;finallySourceStream.Free;end;excepton E: Exception dobegin// 在实际应用中,这里可以添加日志记录Result := False;ErrMsg := E.Message;// 如果目标文件已创建但提取失败,删除它if FileExists(DestFileName) thenDeleteFile(PChar(DestFileName));end;end;
end;
