Welcome to denMike's tiny page!
Home
Guestbook
Contact
 
Husky
 
Corona virus
Electronics
Fantasy
mtech.dk
Programming
  Resources in .exe Files

  Pascal Runtime Error 200

  The Pentium F0 bug

  Number Printing Routing

  Using the IOE Interrupt

 
Software
Space
Tilbud
Various Links
 
USA 2001
ISU MSS03
USA 2006
South Africa 2008


Last updated: 26th March 2000
Appending resources to the end of an .exe file.

Contents of this document
Introduction
The automatic approach
An resource-fileformat
Saving .BGI files as a resource
Example program

Introduction
This document shows how you can add a resource to the end of an .exe file, including some tips to keep track of the resource files, and a neat way to copy the resources 100% automatic.

The automatic approach
I didn't want to run a separate utility to copy the resources every time I recompiled the program. Therefore I let the program check whether or not the resources are located in the file. This happens everytime the program loads. If the resources are not located in the end of the file we'll let the program try to copy them. That is: Every time the program is recompiled the resources needs to be copied, which is automatically done the first time you run the program. Therefore DO NOT ship a program without first running it :-)

The pseudo-code look like this:

begin
  if not IDPresent then
    CopyResources;
  LoadResources;
  RunProgram;
end.

An resource-fileformat
If you only need to add one file to your .exe file you only need to append the original size of the file (the position where your resourcefile start), the resourcefile itself, and an ID to verify whether the resource is present or not.

You might however need to add more files, and therefore it is convenient to save the size of the resource too. One approach is this:

FilePosition : Content
--------------------------------------
0000         : Original .EXE program
xxxx         : Resource file1 size
xxxx+4       : Resource file1
yyyy         : Resource file2 size
yyyy+4       : Resource file2
end-6        : Size of original file = Resource start pos
end-2        : ID (ie. 0E0Fh)

When the program loads it must first check whether the ID is present, if it's not it must copy the resources. Now we know that everything is in place, so we read the resource start position, which is located just before the ID in the end of the file. The program now seeks to that position, reads the size of resource 1, and then the resource itself, continueing with resource 2 etc. You should try to find a unique ID for each of your programs.

The above fileformat is the one I've implemented in the two examples below. This works well, if you want or need to load the entire resource collection every time the program is loaded. You might however need to load only some of the resources, in which case an index of the resourcefile positions will come in handy. This may greatly increase the loading of the resources. You may even save the index as a resource itself:

FilePosition : Content
--------------------------------------
0000         : Original .EXE program
xxxx         : INDEX
yyyy         : Resource file1 size
yyyy+4       : Resource file1
...          : ...
end-6        : Size of original file = Resource start pos
end-2        : ID (ie. 0E0Fh)

The index may be a simple array of positions of the resources in the file, and you don't even have to save the size of the index.

Saving .BGI files as a resource
.BGI files loaded from an resource needs to be registered before you can use them. It's quite easy:

var
  EgaVga : Pointer;
begin
  ...
  { Load EgaVga here }

  { Register the BGI driver: }
  if RegisterBGIdriver(EgaVga) < 0 then begin
    WriteLn('Error registering driver: ', GraphErrorMsg(GraphResult));
    Halt(1);
  end;
  ...
  { Use EgaVga here }
  ...
  { Free EgaVga memory here }
end.

Example program
The following program demonstrates the automatic copying of resources, the indexed resource fileformat, and how to access .BGI drivers as a resource.

You need two additional files in the directory where this program is first run. These are EGAVGA.BGI renamed to EGAVGA.TST and a text file called TEXT.DAT, which includes one line of text for example:

This demonstration came from http://fly.to/thomsen

These two files are appended to the .exe file when the program is executed for the first time after a compilation. The next time the program is run they are not needed.

program Resource;

{ This program uses this internal resource file-format:

     FilePosition : Content
     --------------------------------------
     0000         : Original .EXE program
     xxxx         : INDEX
     yyyy         : Resource file1 size  ( EGAVGA.BGI )
     yyyy+4       : Resource file1
     zzzz         : Resource file2 size  ( TEXT.DAT )
     zzzz+4       : Resource file2
     end-6        : Size of original file = Resource start pos
     end-2        : ID (0E0Fh)
}

{ I've renamed egavga.bgi to egavga.tst to be sure that the system
  doesn't load egavga.bgi from the current directory. }

uses
  Graph;

var
  ResIndex : array[1..2] of LongInt;

procedure CopyResources;
{ This procedure handles the copying, files may be larger than 64K. }
var
  Exe : file;
  M : Pointer;

  procedure IntCopyRes(No:Byte;FName:String);
  var
    res : File;
    ResSize : LongInt;
    BytesRead : Word;
  begin
    assign(res, FName);
  {$I-}
    reset(res,1);                            { open resource }
    if IOResult <> 0 then begin
      Writeln('ERROR: Resource '''+FName+''' not present.');
      Writeln('Contact the programmer, he''s made a mistake!');
      halt;
    end;
  {$I+}

    ResIndex[No] := filesize(exe);
    ResSize := filesize(res);                { write resource size }
    BlockWrite(exe, ResSize,4);

    repeat                                   { append resource to ourself }
      BlockRead(res, M^, $FFFF, BytesRead);
      BlockWrite(exe, M^, BytesRead)
    until BytesRead = 0;

    close(res);
  end;

var
  ResPos : LongInt;
  ID : Word;
begin
  Writeln('Copying resources...');
  Getmem(M,$FFFF);
  assign(exe,paramstr(0));                   { open ourself }
  reset(exe,1);
  ResPos := filesize(exe);
  seek(exe,ResPos);                          { seek to the end }
  blockwrite(exe,ResIndex,sizeof(ResIndex)); { reserve space for index }

  IntCopyRes(1,'egavga.tst');
  IntCopyRes(2,'text.dat');

  blockwrite(exe,ResPos,4);                  { write resource pos }
  ID := $0E0F;
  blockwrite(exe,id,2);                      { write id }

  seek(exe,ResPos);
  blockwrite(exe,ResIndex,sizeof(ResIndex)); { write index }

  close(exe);
  Freemem(M,$FFFF);
end;

function ResourcePresent: Boolean;
{ Returns true if the ID is found in the end of the file }
var
  Exe : file;
  ID : Word;
begin
  assign(exe,paramstr(0));
  reset(exe,1);
  seek(exe,filesize(exe)-2);

  blockread(exe,ID,2);

  close(exe);
  ResourcePresent := ID = $0E0F;
end;

procedure LoadResourceIndex;
{ Loads the index }
var
  Exe : file;
  ResPos : LongInt;
  L : Integer;
begin
  if not ResourcePresent then
    CopyResources;

  assign(exe,paramstr(0));
  reset(exe,1);
  seek(exe,filesize(exe)-6);

  blockread(exe,ResPos,4);                   { read index position }
  seek(exe,ResPos);
  blockread(exe,ResIndex,sizeof(ResIndex));  { read index }

  close(exe);
end;

procedure LoadResource(No: Byte; var P: Pointer; var Size: LongInt);
{ Loads the resource by finding the position in the index,
  memory is allocated HERE, max resourcesize = 64K. }
var
  Exe : file;
  ResSize : LongInt;
  L : Integer;
begin
  assign(exe,paramstr(0));
  reset(exe,1);
  seek(exe,ResIndex[No]);

  blockread(exe,ResSize,4);                  { read resource size }
  Size := ResSize;
  getmem(P,ResSize);
  blockread(exe,P^,ResSize,L);               { read resource <= 64K }

  close(exe);
end;

var
  EgaVgaSize, StrSize : LongInt;
  EgaVga : Pointer;
  grErrCode, grDriver, grMode: Integer;
  Str : pointer;
  A : Byte;
  S : String;
begin
  LoadResourceIndex;
  LoadResource(1, EgaVga, EgaVgaSize);

  { Register the BGI driver: }
  if RegisterBGIdriver(EgaVga) < 0 then begin
    WriteLn('Error registering driver: ', GraphErrorMsg(GraphResult));
    Halt(1);
  end;

  { Init graphics: }
  InitGraph(grDriver, grMode,' ');      { autodetection, though only EGAVGA }
  grErrCode := GraphResult;                                  { is supported }
  if grErrCode = grOk then begin        { Do graphics }
    Line(0, 0, GetMaxX, GetMaxY);
    OutText('Driver loaded by user program');

    { The string resource is not needed until now, so we dynamically load
      it here: }
    LoadResource(2, Str, StrSize);
    S := '';
    for A := 0 to StrSize-1 do
      S := S+char(mem[Seg(Str^):Ofs(Str^)+A]);
    OutTextXY(0,470,S);
    FreeMem(Str, StrSize); { Dynamically free string resource memory }

    Readln;
    CloseGraph;
  end
  else
    Writeln('Graphics error:', GraphErrorMsg(grErrCode));

  { We MUST free the resource memory when done: }
  FreeMem(EgaVga, EgaVgaSize);
end.