The Paradox file structure
--------------------------

This  document  illustrates the binary  file  format  for  Paradox
tables.  There  are still a few unknowns, but the  most  important
items are covered.

The  older table format is referred to as 3.0 tables and the newer
ones as 4.0, 5.0 and 7.0.

Pascal terms are used to describe data types:

    Byte   : 1 byte unsigned
    Integer: 2-byte signed integer
    Word   : 2-byte unsigned integer
    LongInt: 4-byte signed integer
    Char   : 1-byte character
    PChar  : pointer to a character
    ^      : modifies any type definition to a pointer to that type
    ^PChar : pointer to a pointer to a character

All pointers are 4-byte far pointers.

General file structure

Header
------------
Data block 0
Data block 1
Data block 2
...
Data block n
------------

The  size  of  the Paradox file header varies depending  upon  the
number  of fields contained in the table (see HeaderSize  at  file
offset $0002). The first portion (offset $0000 to $0057) has fixed
field locations. The next section (offset $0058 to $0077) was  new
to Paradox 4+ data files.

Some of the informations in the file header seem to be needed only
while  it  is being used by Paradox in RAM. A few of these  fields
are  pointers  to other fields in the header, and are  valid  only
during  run-time.  They  are listed here  anyway,  although  their
meaning  is  subject to change and they serve little  purpose  for
third-party software.

The  data area which follows is divided into record blocks.  These
use  1024,  2048,  3072  or 4096 bytes each,  depending  upon  the
maximum  table  size  set when the table was  created.  The  field
structure  of  the data area is itself unusual in that  each  data
field is arranged in high-byte to low-byte order.

Note:  This high-byte to low-byte arrangement only applies to  the
user  data in the table. Everything else uses the normal  low-byte
to high-byte format.

The  structure  of  primary and secondary  index  files  generally
follows that of Paradox version 3 data files.
 Paradox common file header (offset $0000 to $0057):

With some noted exceptions, this part of the description is common
to  data  and  index  files. Offsets in this  list  are  given  in
hexadecimal. Other  numbers should be assumed as listed in decimal
format unless preceded by a dollar sign ($).

Offset     Type        Name                    Usage
---------------------------------------------------------------------------
0000       Integer     RecordSize              Size  of  a  user record  in
                                               this   table.  For   primary
                                               index files, each record  is
                                               actually the field or fields
                                               in  the  index,  plus  three
                                               integers   which   are   not
                                               referenced  in  the  header.
                                               Secondary  index files  also
                                               have additional fields,  but
                                               these  are  listed  in   the
                                               header.

0002       Integer     HeaderSize              You  can  change  HeaderSize
                                               and  move  the  data  blocks
                                               accordingly,    to    create
                                               larger   or  smaller   table
                                               headers.  Borland's TUTILITY
                                               program would flag an error,
                                               but   Paradox,  the  Paradox
                                               Engine   and   the   Borland
                                               Database  Engine  will   all
                                               still    work   with   these
                                               tables.

0004       Byte        FileType                0 = Indexed .DB data file
                                               1 = Primary index .PX file
                                               2 = Non indexed .DB data
                                                   file
                                               3 = Case sensitive non
                                                   incrementing secondary
                                                   index .Xnn file
                                               4 = Secondary index .Ynn
                                                   file (inc or non inc)
                                               5 = Case sensitive
                                                   incrementing secondary
                                                   index .Xnn file
                                               6 = Non incrementing
                                                   secondary index .XGn
                                                   file
                                               7 = Secondary index .YGn
                                                   file (inc or non inc)
                                               8 = Incrementing secondary
                                                   index .XGn file

0005       Byte        MaxTableSize            Maximum      table      size
                                               determined  when this  table
                                               was   created.   It   really
                                               indicates the size  of  each
                                               block of records in the data
                                               section of the table:

                                               1 = 64M (block size = $0400
                                                   bytes)
                                               2 = 128M (block size = $0800
                                                   bytes)
                                               3 = 192M (block size = $0C00
                                                   bytes)
                                               4 = 256M (block size = $1000
                                                   bytes)

                                               Incrementing the block  size
                                               beyond $1000 in multiples of
                                               1024     in     the      BDE
                                               configuration seems to  have
                                               no  effect on this byte that
                                               remains always 4.

0006       LongInt     NumRecords              Number  of  records  in  the
                                               table.

000A       Word        NextBlock               It  seems to be the same  as
                                               FileBlocks, unless there  is
                                               an empty block in the table.

000C       Word        FileBlocks              Number of data blocks in the
                                               file   (each  block   is   a
                                               cluster of records).

000E       Word        FirstBlock              Always  1, unless the  table
                                               is empty.

0010       Word        LastBlock               Number  of blocks  that  the
                                               table would contain if every
                                               block was packed.

0012       Word        Unknown                 ???

0014       Byte        ModifiedFlags1          A rebuild is required if not
                                               zero.

0015       Byte        IndexFieldNumber        In   the  .Xnn  file  of   a
                                               secondary  index, number  of
                                               the field it is referencing.
                                               Zero in the other files.

0016       Pointer     PrimaryIndexWorkspace   Pointer to the primary index
                                               file header (in RAM). NIL if
                                               there is no primary index.

001A       Pointer     Unknown                 Usually  a  NIL pointer.  It
                                               seems to be used only in  5+
                                               tables with BCD field types.
                                               It   is   probably  just   a
                                               workspace pointer.

001E..0020 3 bytes     Unknown                 It  seems  that these  three
                                               bytes  are used in .PX files
                                               only.

0021       Integer     NumFields               Number  of  fields  in   the
                                               table.  For index files,  it
                                               would   be  the  number   of
                                               fields in this index.

0023       Integer     PrimaryKeyFields        Number  of  fields  in   the
                                               file's primary key. Is  zero
                                               for .PX and .Ynn files and 2
                                               for   .Xnn  secondary  index
                                               files.

0025       LongInt     Encryption1             Encryption  information  for
                                               versions  3.0 and 3.5  (zero
                                               if      not      encrypted).
                                               Subsequent  versions   store
                                               the  value  $FF00FF00  here,
                                               and move the encryption code
                                               to  offset $005C.  Even  so,
                                               these  newer versions  still
                                               maintain this information at
                                               both locations while working
                                               in RAM.

                                               Primary  and .Ynn  secondary
                                               index files always use  this
                                               field    to    store     the
                                               encryption code, but  it  is
                                               normally   a  zero   because
                                               index    files    are    not
                                               encrypted.  You can  encrypt
                                               index   files  by  following
                                               these steps:

                                               - Begin  with  an  empty
                                                 encrypted table.
                                               - Truncate the index files
                                                 to HeaderSize.
                                               - Put NextBlock, FileBlocks,
                                                 FirstBlock and LastBlock
                                                 to zero.
                                               - Copy four bytes from the
                                                 .DB data file Encryption1
                                                 field (for version 3), or
                                                 the Encryption2 field (for
                                                 versions 4 and above) into
                                                 the Encryption1 or
                                                 Encryption2 field of the
                                                 index file.
                                               - Test thoroughly.

                                               Paradox  encryption  is  not
                                               secure.  It can be  bypassed
                                               very easily.

0029       Byte        SortOrder               $00 = ASCII
                                               $B7 = International
                                               $82 = Norwegian/Danish
                                               $E6 = Norwegian/Danish (4.0)
                                               $F0 = Swedish/Finnish

002A       Byte        ModifiedFlags2          A rebuild is required if not
                                               zero.

002B..002C 2 bytes     Unknown                 Always zero

002D       Byte        ChangeCount1            Incremented   whenever   the
                                               file header is updated.

002E       Byte        ChangeCount2            ???

002F       Byte        Unknown                 ???

0030       ^PChar      TableNamePtrPtr         Pointer   to   TableNamePtr,
                                               which  is a pointer to table
                                               name.   Paradox  uses   this
                                               field  to gain faster access
                                               to  table name because  that
                                               part   of   the  header   is
                                               accessed sequentially.

0034       Pointer     FldInfoPtr              Pointer to the list of field
                                               identifiers. This is  listed
                                               in    the    Pascal   record
                                               definition  as a PFldInfoRec
                                               type.   You  can  use   this
                                               pointer value to locate  the
                                               table   header   in   memory
                                               during   run   time.    Just
                                               subtract  $0078  from   this
                                               value (for 4.0+ tables),  or
                                               $0058  (for  .PX  and   .Ynn
                                               index files and version  3.0
                                               tables).

0038       Byte        WriteProtected          0 = write protection OFF
                                               1 = write protection ON

0039       Byte        FileVersionID           $03 = version 3.0
                                               $04 = version 3.5
                                               $05 = version 4.x
                                               $06 = version 4.x
                                               $07 = version 4.x
                                               $08 = version 4.x
                                               $09 = version 4.x default
                                               $0A = version 5.x
                                               $0B = version 5.x default
                                               $0C = version 7.x

003A       Word        MaxBlocks               Usually    the    same    as
                                               FileBlocks    (at     offset
                                               $000C).

003C       Byte        Unknown                 ???

003D       Byte        AuxPasswords            Number      of     auxiliary
                                               passwords  assigned  to  the
                                               table.

003E..003F 2 bytes     Unknown                 ???

0040       Pointer     CryptInfoStartPtr       Points  to CryptInfo  field.
                                               Always    NIL    when    not
                                               encrypted.  It is  sometimes
                                               NIL even when encrypted.

0044       Pointer     CryptInfoEndPtr         Points  to end of CryptInfo.
                                               It is NIL if not encrypted.

0048       Byte        Unknown                 ???

0049       LongInt     AutoIncVal              Current  value  for  any +/-
                                               AutoInc    field   (formerly
                                               ChangeCount3).

004D..004E 2 bytes     Unknown                 ???

004F       Byte        IndexUpdateRequired     ???

0050..0054 5 bytes     Unknown                 ???

0055       Byte        Descending              In   secondary  .Xnn   index
                                               files:

                                               $01 = Normal ascending sort
                                                     order
                                               $11 = Descending sort order
                                                     (7+ only)

0056..0057 2 bytes     Unknown                 ???
---------------------------------------------------------------------------

Paradox 4+ data file header (offset $0058 to $0077):
----------------------------------------------------

This  part  of the description applies only to .DB data files  and
.Xnn index files for Paradox versions 4.0+.

Offset     Type        Name                    Usage
---------------------------------------------------------------------------
0058       Integer     FileVersionID (?)       $0105 = version 4.x
                                               $0106 = version 4.x
                                               $0107 = version 4.x
                                               $0108 = version 4.x
                                               $0109 = version 4.x default
                                               $010A = version 5.x
                                               $010B = version 5.x default
                                               $010C = version 7.x

005A       Integer     FileVersionID (?)       Same as $0058

005C       LongInt     Encryption2             Zero if not encrypted.

0060       LongInt     FileUpdateTime          4.x only (?). Format similar
                                               to  a  packed date and time.
                                               Unknown in 5+ tables.

0064       Integer     HiFieldID               Always NumFields + 1.

0066       Integer     HiFieldIDInfo (?)       Related     to     HiFieldID
                                               (above), but unknown.

0068       Integer     NumFields2 (?)          Sometimes   the  number   of
                                               fields in the table, but  is
                                               often just a zero.

006A       Integer     DosGlobalCodePage       DOS  global code  page  when
                                               the table was created.

006C..006F 4 bytes     Unknown                 ???

0070       Integer     ChangeCount4            ???

0072..0077 6 bytes     Unknown                 ???
---------------------------------------------------------------------------

Paradox common file header (continued):
---------------------------------------

The file header continues sequentially. Since the number of fields
varies, there are no subsequent fixed offsets.

This section begins where the previous sections left off:

Offset:

0058: Paradox tables version 3.0 and 3.5
0058: Paradox .PX and .Ynn index files (any listed version)
0078: Paradox tables version 4.0 and above
0078: Paradox .Xnn index files version 4.0 and above

Field types and sizes:

FldType FldSize (dec) Letter(s)  Name
----------------------------------------------------
$01     varies        A          Alpha
$02     4             D          Date
$03     2             S          Short Integer
$04     4             I          Long Integer
$05     8             $          Money
$06     8             N          Number
$09     1             L          Logical
$0C     varies        M          Memo blob
$0D     varies        B          Binary blob
$0E     varies        F          Formatted memo blob
$0F     varies        O          Ole object blob
$10     varies        G          Graphic object blob
$14     4             T          Time
$15     8             @          Time stamp
$16     4             +/-        Auto Increment
$17     17*           #          BCD
$18     varies        Y          Bytes
----------------------------------------------------

*The  FldSize  given for BCD fields is not used  for  field  size.
Instead,  FldSize  denotes  the number  of  digits  following  the
decimal point. BCD fields are always 17 bytes long.

Type                                 Name              Notes
---------------------------------------------------------------------------
array[1..NumFields] of TFldInfoRec   FieldInfo         See  field types and
                                                       sizes    in    table
type                                                   below.
  TFldInfoRec = record
    FldType: Byte;
    FldSize: Byte
  end;

PChar                                TableNamePtr      Pointer   to   table
                                                       name when header  is
                                                       in RAM.

array[1..NumFields] of PChar         FieldNamePtrArray Array   of  pointers
                                                       that  reference  the
                                                       field   names   when
These pointers are not                                 Paradox (or  one  of
present in the index                                   the   engines)    is
files.                                                 running. The size of
                                                       this  array  depends
                                                       upon  the number  of
                                                       fields.

array[1..79] of Char                 TableName         The  name this  file
                                                       was   assigned  when
                                                       created. It will use
                                                       79   bytes,   padded
                                                       with   zeroes.   You
                                                       will often find  the
                                                       file            name
                                                       RESTTEMP.DB here.
---------------------------------------------------------------------------

The rest of this information doesn't apply to .PX and .Ynn files:

Type                                 Name              Notes
---------------------------------------------------------------------------
Char[]                               FieldNames        These are the ASCIIZ
                                                       field        name(s)
                                                       arranged
                                                       sequentially.   They
                                                       are  the  fields  of
                                                       the   table  or  the
                                                       fields    of     the
                                                       secondary index.

record                               CryptInfo         Encrypted     tables
                                                       would           have
                                                       additional      data
                                                       here.

                                                       Tables          with
                                                       auxiliary  passwords
                                                       would have 256 bytes
                                                       here,   about  which
                                                       there     are     no
                                                       information.

                                                       Encrypted     tables
                                                       without    auxiliary
                                                       passwords would have
                                                       one  byte  for  each
                                                       field here.

array[1..NumFields] of Integer       FieldNumbers      In  secondary  index
                                                       .Xnn files only:

                                                       Field   numbers   by
                                                       which    index    is
                                                       composed.  The  size
                                                       is determined by the
                                                       number of fields  in
                                                       the index.

Char[]                               SortOrderID       An   ASCIIZ   string
                                                       representing     the
                                                       sort  order for this
                                                       table  (ascii, intl,
                                                       etc.).

Char[]                               IndexName         In  secondary  index
                                                       .Xnn files only:

                                                       An   ASCIIZ   string
                                                       representing     the
                                                       name     of      the
                                                       secondary     index.
                                                       It's the name of the
                                                       field    for    case
                                                       sensitive one  field
                                                       indexes, or the name
                                                       supplied by the user
                                                       when  the index  was
                                                       created.
---------------------------------------------------------------------------

Paradox data blocks:
--------------------

The  data  area  begins at offset HeaderSize. It is  divided  into
blocks  of 1024, 2048, 3072 or 4096 bytes (and more for 7+ tables,
still  not  verified), depending upon the maximum table  size  set
when the table was created.

The entire data block will be encrypted if the table is encrypted.

Offset     Type        Name                    Usage
---------------------------------------------------------------------------
0000       Word        NextBlock               Block  number + ???.  It  is
                                               actually  unknown.

0002       Word        BlockNumber             The  first block is numbered
                                               zero.

0004       Integer     AddDataSize             This  represents the  amount
                                               of  data  in this block,  in
                                               addition   to   one   record
                                               length. This will be a  zero
                                               if  there  is one record  in
                                               this  block, and a  negative
                                               number   if  there  are   no
                                               records:

                                                    NumRecsInBlock :=
                                               AddDataSize / RecordSize + 1

0006..     bytes       FileData                Block  size varies according
                                               to MaxTableSize (at $0005 in
                                               the first table):

                                               MaxTableSize = 1: size $0400
                                               MaxTableSize = 2: size $0800
                                               MaxTableSize = 3: size $0C00
                                               MaxTableSize = 4: size $1000

                                               The records in the data area
                                               are arranged in field order.
                                               Alpha  fields are arrays  of
                                               characters,   padded    with
                                               zeroes. Other fields seem to
                                               be  arranged in high-byte to
                                               low-byte  order,  with   the
                                               first  byte's high  bit  set
                                               for positive numbers.

                                               For  example, the  number  1
                                               stored  as  a  2-byte  Short
                                               Integer  field  is  arranged
                                               this  way: $80,01.  And  the
                                               integer 256 would be  stored
                                               as: $81,00.

                                               Floating  point  Number  and
                                               currency  fields are  8-byte
                                               double  types, high-byte  to
                                               low-byte,  with  the   first
                                               byte's high bit set.

                                               Date  fields are  stored  as
                                               the number of days since JAN
                                               1, 0001 in high-byte to low-
                                               byte  order with  the  first
                                               byte's high bit set.

                                               The first byte's high bit is
                                               set  for  positive  numbers,
                                               because   a   blank    Short
                                               Integer (2-byte) is assigned
                                               the value of $8000. By using
                                               the  high-byte  to  low-byte
                                               format, and by reversing the
                                               first  byte's high bit,  the
                                               value $8000 is converted  to
                                               all   zeroes.  So  a  record
                                               stored  as  all  zeroes   is
                                               actually all blanks.

                                               The records in primary index
                                               files  contain the field  or
                                               fields  in  the index,  plus
                                               three integers that are  not
                                               described  in  the  header's
                                               FieldInfo area.
