当前位置: 首页 > news >正文

【Delphi】再谈给EXE文件动态添加数据(附加大数据 2G)

        

        我曾在《【Delphi】如何在可执行文件中动态添加数据》讨论过如何通过Delphi程序给已经存在的EXE程序添加数据。当时使用的是对象TPayload对象,通过TPayloadFooter水印给EXE添加数据,使用起来是没有任何问题的。但是有一点小小的限制,那就是吸入数据和读出数据,使用的都是TBytes结构,这样受制于内存的大小等无法实现附加大数据,比如附加2G的数据。

        而本文要讨论的就是不直接使用TBytes,而是使用TFileStream,这样就可以附加大数据,比如2G的数据。同时增加了对TPayloadFooter的校验,更加可靠。

        当然,为了方便使用,再次提供了TBytes参数的函数,这也会受到内存大小的限制!

        根据实际情况,我们设计4个函数,两两一对,分别支持文件类型和TBytes类型。

函数列表
序号函数名称说明
1.1function AppendFileToExe(const F1FileName, F2FileName: string; out ErrMsg : string): Boolean;将文件F2FileName附加到文件F1FileName的末尾, 成功True,失败则False,同时Errmsg包含错误信息
1.2function ExtractFileFromExe(const SourceFileName, DestFileName: string; out ErrMsg : string): Boolean;对应上述函数,从文件SourceFileName尾部解析出文件DestFileName,解析的条件是根据水印 ExpectedWaterMark。成功True,失败则False
2.1function AppendBytesToExe(const F1FileName: string; F2Bytes : TBytes; out ErrMsg : string): Boolean;将数据流F2Bytes附加到文件F1FileName的末尾, 成功True,失败则False,同时Errmsg包含错误信息
2.2function 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;

二、整个工程代码

1. exe程序
2. 源代码

http://www.dtcms.com/a/564918.html

相关文章:

  • 1、Python-内置函数、Pass空语句
  • 为什么 Transformer 使用 LayerNorm 而不是 BatchNorm?
  • 昇腾 NPU 实战:Gemma 2 模型部署、多维度评测与性能优化全流程解析
  • 使用Visual Studio中的数据断点快速定位内存越界问题的实战案例分享
  • 做网站的大创结项项目建设表态发言稿
  • 家政类网站开发成本青岛最新疫苗接种
  • B站点击埋点数据分析
  • 用 PyTorch 训练 NestedUNet 分割细胞核
  • [7-01-02].第10节:开发应用 - 配置Kafka中消费消息策略
  • 学算法不再卡壳,Hello-Algo 让学习有画面感,cpolar远程访问更添便利
  • openEuler安装docker及其配置
  • 重组蛋白纯化标签:高效获取高质量蛋白的关键技术
  • agent智能体
  • 网站制作珠海公司asp 网站管理工具
  • html网站地图怎么做企业网站建设主要考虑哪些
  • 深入昇腾NPU:从架构到算子开发的全栈探索
  • Rust编程学习 - 内存分配机制,如何动态大小类型和 `Sized` trait
  • JavaSe—List集合系列
  • 1.1 大语言模型调用方式与函数调用(Function Calling):从基础到实战
  • 在Windows系统上部署 CosyVoice 2
  • kafka kraft 模式简介
  • 【Html模板】赛博朋克风格数据分析大屏(已上线-可预览)
  • 怎么查网站备案号济南市章丘区建设局网站
  • 颠覆编码范式:Cursor 2.0五大新特性深度解析与AI编程未来洞察
  • 科技引领,档案管理更高效之智慧档案馆三维立体恒温恒湿消毒净化系统
  • MySQL的SUBSTRING函数详解与应用
  • 微企点建站效果付费合肥网络推广外包
  • 企业级管理平台项目设计、架构、业务全解之平台篇
  • android TAB切换
  • 免费试用网站源码上海网站建设穹拓