【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;
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;

二、整个工程代码

1. exe程序
2. 源代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海纳老吴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值