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.
|