2007. november 14., szerda

Easy EXE attached data


Problem/Question/Abstract:

Using resources to include files into your EXE is a great thing, but I prefer using another way...

Answer:

When you compiled your code, and you want to attach 1 file to your file just use this command line:

COPY /B PROJECT1.EXE + DATA.TXT PROJECT2.EXE

A file, called Project2.Exe will be created and it will contain first file with 2nd file attached. That doesn't compromise EXE and it will be still working.

But: how to easly extract that file form the EXE?

First of all, you should use this function:

function GetAttachedData(MS: TMemoryStream): boolean;
var
  pMySelf: pChar;
  IdX, SectionsCount: integer;
  EXESize, EXEOriginalSize: cardinal;
  SR: TSearchRec;
  FS: TMemoryStream;
  EXEName: array[0..MAX_PATH] of char;
begin
  result := false;
  if MS = nil then
    exit;
  try
    MS.clear;
    // Gets EXE/DLL filename.
    fillchar(EXEName, sizeof(EXEName), #0);
    getmodulefilename(HInstance, EXEName, MAX_PATH);
    // Gets file size.
    EXESize := 0;
    if findfirst(EXEName, faAnyFile, SR) = 0 then
    begin
      EXESize := SR.size;
      sysutils.findclose(SR);
    end;
    // Gets originalsize.
    EXEOriginalSize := 0;
    try
      pMySelf := pointer(HInstance);
      if PImageDosHeader(pMySelf).E_Magic <> $00004550 then
        exit;
      inc(pMySelf, PImageDosHeader(pMySelf)._lfanew);
      if pDWord(pMySelf)^ <> $00004550 then
        exit;
      inc(pMySelf, sizeof(dword));
      SectionsCount := PImageFileHeader(pMySelf).NumberOfSections;
      inc(pMySelf, sizeof(TImageFileHeader) + sizeof(TImageOptionalHeader));
      for IdX := 1 to SectionsCount do
      begin
        with PImageSectionHeader(pMySelf)^ do
          if (PointerToRawData + SizeOfRawData) > EXEOriginalSize then
            EXEOriginalSize := PointerToRawData + SizeOfRawData;
        inc(pMySelf, sizeof(TImageSectionHeader));
      end;
    except
      on e: exception do
        EXEOriginalSize := 0;
    end;
    // If there's something attached...
    if EXESize > EXEOriginalSize then
    begin
      FS := TMemoryStream.create;
      try
        try
          // Read it...
          FS.loadfromfile(EXEName);
          FS.position := EXEOriginalSize;
          // and return it in the stream.
          MS.copyfrom(FS, EXESize - EXEOriginalSize);
          result := true;
        except
          on e: exception do
            result := false;
        end;
      finally
        FS.destroy;
      end;
    end;
  except
    on e: exception do
      result := false;
  end;
end;

Then, you can use it like this:

procedure TForm1.Button1Click(Sender: TObject);
var
  s: tmemorystream;
begin
  s := tmemorystream.create;
  try
    if GetAttachedData(s) then
      s.SaveToFile('output.txt');
  finally
    s.destroy;
  end;
end;

That's all. You don't need to use constants, resources or anything else.
Note that the code will work also if the EXE file is compressed with tools like UPX or similar.
Thanks to the site U.N.D.U. for some code snippets.

Nincsenek megjegyzések:

Megjegyzés küldése