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:
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.
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:
For 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 |
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:
A complete list of entries and what their fields contain can be found in ppudump.pp.
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.