Subsections


A. Anatomy of a unit file

A.1 Basics

The best and most updated documentation about the ppu files can be found in ppu.pas and ppudump.pp which can be found in rtl/utils/.

To read or write the ppufile, you can use the ppu unit ppu.pas which has an object called tppufile which holds all routines that deal with ppufile handling. While describing the layout of a ppufile, the methods which can be used for it are presented as well.

A unit file consists of basically five or six parts:

  1. A unit header.
  2. A file interface part.
  3. A definition part. Contains all type and procedure definitions.
  4. A symbol part. Contains all symbol names and references to their definitions.
  5. A browser part. Contains all references from this unit to other units and inside this unit. Only available when the uf_has_browser flag is set in the unit flags
  6. A file implementation part (currently unused).

A.2 reading ppufiles

We will first create an object ppufile which will be used below. We are opening unit test.ppu as an example.

var
  ppufile : pppufile;
begin
{ Initialize object }
  ppufile:=new(pppufile,init('test.ppu');
{ open the unit and read the header, returns false when it fails }
  if not ppufile.open then
    error('error opening unit test.ppu');

{ here we can read the unit }

{ close unit }
  ppufile.close;
{ release object }
  dispose(ppufile,done);
end;

Note: When a function fails (for example not enough bytes left in an entry) it sets the ppufile.error variable.

A.3 The Header

The header consists of a record containing 24 bytes:

tppuheader=packed record
    id       : array[1..3] of char; { = 'PPU' }
    ver      : array[1..3] of char;
    compiler : word;
    cpu      : word;
    target   : word;
    flags    : longint;
    size     : longint; { size of the ppufile without header }
    checksum : longint; { checksum for this ppufile }
  end;

The header is already read by the ppufile.open command. You can access all fields using ppufile.header which holds the current header record.

field description
id this is allways 'PPU', can be checked with function ppufile.CheckPPUId:boolean;
ver ppu version, currently '015', can be checked with function ppufile.GetPPUVersion:longint; (returns 15)
compiler compiler version used to create the unit. Doesn't contain the patchlevel. Currently 0.99 where 0 is the high byte and 99 the low byte
cpu cpu for which this unit is created. 0 = i386 1 = m68k
target target for which this unit is created, this depends also on the cpu!

For i386:
0 Go32v1
1 Go32V2
2 Linux-i386
3 OS/2
4 Win32

For m68k:
0 Amiga
1 Mac68k
2 Atari
3 Linux-m68k

flag the unit flags, contains a combination of the uf_ constants which are definied in ppu.pas
size size of this unit without this header
checksum checksum of the interface parts of this unit, which determine if a unit is changed or not, so other units can see if they need to be recompiled

A.4 The sections

After this header follow the sections. All sections work the same! A section consists of entries and ends also with an entry, but containing the specific ibend constant (see ppu.pas for a list of constants).

Each entry starts with an entryheader.

  tppuentry=packed record
    id   : byte;
    nr   : byte;
    size : longint;
  end;

field Description
id this is 1 or 2 and can be checked to see whether the entry is correctly found. 1 means its a main entry, which says that it is part of the basic layout as explained before. 2 means that it it a sub entry of a record or object.
nr contains the ib constant number which determines what kind of entry it is.
size size of this entry without the header, can be used to skip entries very easily.

To read an entry you can simply call ppufile.readentry:byte, it returns the tppuentry.nr field, which holds the type of the entry. A common way how this works is (example is for the symbols):

  repeat
    b:=ppufile.readentry;
    case b of
   ib<etc> : begin
             end;
 ibendsyms : break;
    end;
  until false;

Then you can parse each entry type yourself. ppufile.readentry will take care of skipping unread bytes in the entry and reads the next entry correctly! A special function is skipuntilentry(untilb:byte):boolean; which will read the ppufile until it finds entry untilb in the main entries.

Parsing an entry can be done with ppufile.getxxx functions. The available functions are:

procedure ppufile.getdata(var b;len:longint);
function  getbyte:byte;
function  getword:word;
function  getlongint:longint;
function  getreal:ppureal;
function  getstring:string;

To check if you're at the end of an entry you can use the following function:

function  EndOfEntry:boolean;
notes:
  1. ppureal is the best real that exists for the cpu where the unit is created for. Currently it is extended for i386 and single for m68k.
  2. the ibobjectdef and ibrecorddef have stored a definition and symbol section for themselves. So you'll need a recursive call. See ppudump.pp for a correct implementation.

A complete list of entries and what their fields contain can be found in ppudump.pp.

A.5 Creating ppufiles

Creating a new ppufile works almost the same as reading one. First you need to init the object and call create:
  ppufile:=new(pppufile,init('output.ppu'));
  ppufile.create;

After that you can simply write all needed entries. You'll have to take care that you write at least the basic entries for the sections:

  ibendinterface
  ibenddefs
  ibendsyms
  ibendbrowser (only when you've set uf_has_browser!)
  ibendimplementation
  ibend

Writing an entry is a little different than reading it. You need to first put everything in the entry with ppufile.putxxx:

procedure putdata(var b;len:longint);
procedure putbyte(b:byte);
procedure putword(w:word);
procedure putlongint(l:longint);
procedure putreal(d:ppureal);
procedure putstring(s:string);

After putting all the things in the entry you need to call ppufile.writeentry(ibnr:byte) where ibnr is the entry number you're writing.

At the end of the file you need to call ppufile.writeheader to write the new header to the file. This takes automatically care of the new size of the ppufile. When that is also done you can call ppufile.close and dispose the object.

Extra functions/variables available for writing are:

ppufile.NewHeader;
ppufile.NewEntry;
This will give you a clean header or entry. Normally this is called automatically in ppufile.writeentry, so there should be no need to call these methods.
ppufile.flush;

to flush the current buffers to the disk

ppufile.do_crc:boolean;
set to false if you don't want that the crc is updated, this is necessary if you write for example the browser data.



root
2000-12-20