This trainer is about reading PCX files, file packing, and putting everything into one executable file.

DENTHOR, coder for ...
_____   _____   ____   __   __  ___  ___ ___  ___  __   _____
/  _  \ /  ___> |  _ \ |  |_|  | \  \/  / \  \/  / |  | /  _  \
|  _  | \___  \ |  __/ |   _   |  \    /   >    <  |  | |  _  |
\_/ \_/ <_____/ |__|   |__| |__|   |__|   /__/\__\ |__| \_/ \_/
smith9@batis.bis.und.ac.za
The great South African Demo Team! Contact us for info/code exchange!  

Grant Smith, alias Denthor of Asphyxia, wrote up several articles on the creation of demo effects in the 90s. I reproduce them here, as they offer so much insight into the demo scene of the time.

These articles apply some formatting to Denthor's original ASCII files, plus a few typo fixes.

Loading a PCX file

This is actually quite easy. The PCX file is divided into three sections, namely a 128 byte header, a data section, and a 768 byte palette.

You can usually ignore the 128 byte header, but here it is:

0  Manufacturer     10 = ZSoft .PCX file
1  Version
2  Encoding
3  Bits Per Pixel
4  XMin, Ymin, XMax, YMax  (2 bytes each)
12 Horizontal Resolution (2 bytes)
14 Vertical Resolution (2 bytes)
16 Color palette setting (48 bytes)
64 Reserved
65 Number of color planes
66 Bytes per line (2 bytes)
68 1 = Color    2 = Grayscale  (2 bytes)
70 Blank (58 bytes)

That makes 128 bytes.

The palette file, which is 768 bytes, is situated at the very end of the PCX file. The 769’th byte back should be the decimal 12, which indicates that a VGA color palette is to follow.

There is one thing that we have to do to get the palette correct in VGA… the PCX palette values for R,G,B are 0 to 255 respectively. To convert this to our standard (VGA) palette, we must divide the R,G,B values by 4, to get them into a range of 0 to 63.

Actually decoding the image is very simple. Starting after the 128 byte header, we read a byte.

If the top two bits of this byte are not set, we dump the value to the screen.

We check bits as follows:

if ((temp and $c0) = $c0) then ...(bits are set)... else ...(bits are not set)

C0 in hex = 11000000 in binary = 192 in decimal

Let’s look at that more closely…

  temp  and  c0h
  temp  and  11000000b

That means, when represented in bit format, both corresponding bits have to be set to one for the result to be one.

0 and 0 = 0     1 and 0 = 0    0 and 1 = 0    1 and 1 = 1

In the above case then, both of the top two bits of temp have to be set for the result to equal 11000000b. If it does not equal that, the top two bits are not both set, and we can put the pixel.

So:

  if ((temp and $c0) = $c0) then BEGIN
  END else BEGIN
    putpixel (screenpos,0,temp,vga);
    inc (screenpos);
  END;

If the top two bits are set, things change. The bottom six bits become a loop counter, which the next byte is repeated.

  if ((temp and $c0) = $c0) then BEGIN
    { Read in next byte, temp2 here.}
    for loop1:=1 to (temp and $3f) do BEGIN
      putpixel (screenpos,0,temp2,vga);
      inc (screenpos);
    END;
  END else BEGIN
    putpixel (screenpos,0,temp,vga);
    inc (screenpos);
  END;

There is our PCX loader. You will note that by and’ing temp by $3f; I am and’ing it by 00111111b… in other words, clearing the top two bits.

The sample program has the above in assembler, but it is the same procedure… and you can read the next tutorial for more info on how to code in assembler.

In the sample I assume that the pic is 320x200, with a maximum size of 66,432 bytes.

File packing

The question is simple: how do I get all my files into one executable? Having many small data files can start to look unprofessional.

An easy way to have one .exe and one .dat file when dealing with many cels etc. is easy… you would alter your load procedure, which looks as follows:

VAR temp : Array [1..5,1..256] of byte;
Procedure Init;
BEGIN
  loadcel ('pic1.cel',temp[1]);
  loadcel ('pic2.cel',temp[2]);
  loadcel ('pic3.cel',temp[3]);
  loadcel ('pic4.cel',temp[4]);
  loadcel ('pic5.cel',temp[5]);
END;

For one compile you would do this:

VAR temp : Array [1..5,1..256] of byte;
Procedure Init;
VAR f:File;
BEGIN
  loadcel ('pic1.cel',temp[1]);
  loadcel ('pic2.cel',temp[2]);
  loadcel ('pic3.cel',temp[3]);
  loadcel ('pic4.cel',temp[4]);
  loadcel ('pic5.cel',temp[5]);
  assign (f,'pic.dat');
  rewrite (f,1);
  blockwrite (f,temp,sizeof(temp));
  close (f);
END;

From then on, you would do:

VAR temp : Array [1..5,1..256] of byte;
Procedure Init;
VAR f:File;
BEGIN
  assign (f,'pic.dat');
  reset (f,1);
  blockread (f,temp,sizeof(temp));
  close (f);
END;

This means that instead of five data files, you now have one! You have also stripped the 800 byte cel header too. Note that this will work for any form of data, not just cel files.

The next logical step is to include this data in the .exe file, but the question is how? In an earlier tutorial, I converted my data file to constants and placed it inside my main program. This is not good mainly because of space restrictions … you can only have so many constants, and what if your data file is two megs big?

Attached with this tutorial is a solution. I have written a program that combines your data files with your executable file, no matter how big the data is. The only thing you have to worry about is a small change in your data loading methods. Let’s find out what.

Using the file packer

Normally you would load your data as follows:

Procedure Init;
BEGIN
  loadcel ('bob.bob',temp);
  loadpcx ('den.pcx',VGA);        { Load a PCX file }
  loaddat ('data.dat',lookup);    { Load raw data into lookup }
END;

Easy, hey? Now, using the file packer, you would change this to:

USES fpack;
Procedure Init;
BEGIN
  total := 3;
  infodat[1] := 'bob.bob';
  infodat[2] := 'den.pcx';
  infodat[3] := 'data.dat';
  loadcel (1,temp);
  loadpcx (2,VGA);
  loaddat (3,lookup);
END;

Not too difficult? Now, this is still using the normal data files on your hard drive. You would then run PACK.EXE, select the program’s .exe as the base, then select “bob.bob”, “den.pcx” and “data.dat”, in order (1, 2, 3). Hit “c” to contine, and it will combine the files. Your programs .exe file will be able to run independently of the separate data files on disk, because the data files are imbedded with the .exe.

Let us take a closer look at the load procedures. Normally a load procedure would look as follows:

Procedure LoadData (name:string; p:pointer);
VAR f:file;
BEGIN
  assign (f,name);
  reset (f,1);
  blockread (f,p^,filesize(f);
  close (f);
END;

In FPack.pas, we do the following:

Function LoadData (num:integer; p:pointer):Boolean;
VAR f:file;
BEGIN
  If pack then BEGIN
    assign (f,paramstr(0));
    reset (f,1);
    seek (f,infopos[num]);
    blockread (f, p^, infopos[num+1]-infopos[num]);
    close (f);
  END else BEGIN
    assign (f,infodat[num]);
    reset (f,1);
    blockread (f, p^, filesize (f));
    close (f);
  END;
END;

As you can see, we just have two special cases depending on whether or not the .exe file has been packed yet.

NOTE: After you have packed the file, you CAN NOT pklite it. You can however pklite the .exe_before you run pack.exe … in other words, you cannot use pklite to try pack your data files.

PACK.EXE does have a limitation … you can only pack 24 data files together. If you would like it to do more, mail me … It should be easy to increase the number.

In the sample program, FINAL.EXE is the same as temp.pas, except it has its PCX embedded inside it. I ran pack2.exe, selected final.exe and eye.pcx, hit “C”, and there it was. You will notice that eye.pcx is not included in the directory … it is now part of the exe!

In closing

Well, that’s about it for this trainer… next one (as I have mentioned twice already ;) will be on assembler, with a flame routine thrown in.

This tut has been a bit of a departure from normal tuts … aside from the PCX loading routines, the rest has been “non programming” oriented … don’t worry, next week’s one will be back to normal.