Clarion DAT файл

Overview
This bulletin explains the format of Clarion data files.

Clarion Data Files
This technical bulletin explains the format of data files created by the
Clarion Professional Developer and the Clarion Personal Developer (Version 2 and
above).  It will only cover data files; key and index files are covered in
Clarion Technical Bulletin #118.  Use this information carefully because an
erroneous change to a data file will make your file unreadable.  Due to the
risks of data integrity, encryption is not covered in this bulletin.
A file defined with Clarion contains a lot of information on data such as
fields, keys, indexes, pictures, arrays, etc.  When the file is first
created, you will have a .DAT file and optionally one or more .K??, .I??, and .MEM
files (where ?? is a two digit number).  The .DAT file also contains
information on keys, fields and their layouts, and many fields that allow
Professional Developer to efficiently use the space on your disk.
First, let us discuss the .DAT file.  This file consists of several
sections (some sections are optional):
1) The file header
2) Field description
3) Key and index descriptors
4) Picture descriptors
5) Array descriptors
6) Data
The file header and field descriptors are always in the file-the other
sections exist only if they are needed.  If there is no data in the file,
section 6 is optional.
Now let us look at each section individually.  To simplify matters, the
picture and array descriptors will be explained later in the bulletin.

1) THE FILE HEADER
The file header consists of 23 fields that instruct the Professional
Developer what items to search for in the rest of the .DAT file.  The
following example is a C structure that defines the file header:
struct {
unsigned filesig; /* file signature */
unsigned sfatr; /* file attribute and status */
unsigned char numbkeys;/* number of keys in file */
unsigned long numrecs;/* number of records in file */
unsigned long numdels;/* number of deleted records */
unsigned numflds; /* number of fields */
unsigned numpics; /* number of pictures */
unsigned nummars; /* number of array descriptors */
unsigned reclen; /* record length (including record header) */
unsigned long offset;/* start of data area */
unsigned long logoef;/* logical end of file */
unsigned long logbof;/* logical beginning of file */
unsigned long freerec;/* first usable deleted record */
unsigned char recname[12];/* record name without prefix */
unsigned char memnam[12];/* memo name without prefix */
unsigned char filpre[3];/* file name prefix */
unsigned char recpre[3];/* record name prefix */
unsigned memolen; /* size of memo */
unsigned memowid; /* column width of memo */
unsigned long reserved;/* reserved */
unsigned long chgtime;/* time of last change */
unsigned long chgdate;/* date of last change */
unsigned reserved2;/* reserved */
{;
When looking at a dump of a .DAT file, remember that fields marked «unsigned»
(otherwise known as «unsigned int») use 2 bytes, «unsigned char»
fields use 1 byte, and «unsigned long» fields use 4 bytes.  Due
to the way the Intel CPUs store their data, bytes of unsigned longs and unsigned
ints are  reversed on disk.
Almost all of the fields in the header are numbers or strings.  One
exception is a bit-map.  The file attribute field (sfatr) is a 2-byte field.
Currently, only the lower 8 bits are used.  Depending on what is currently
happening to the file, the following bits are either turned on or off.
Bit 0 is the low order bit and bit 15 is the high order bit.

If a bit is turned on, the corresponding condition is true for the file:
bit 0 — file is locked
bit 1 — file is owned
bit 2 — records are encrypted
bit 3 — memo file exists
bit 4 — file is compressed
bit 5 — reclaim deleted records
bit 6 — file is read only
bit 7 — file may be created

2) THE FIELD DESCRIPTORS
Field descriptors follow the file header.  There are as many field
descriptor entries as there are fields in the file.  Each field descriptor
consists of 8 fields.  They are defined as follows:
struct {
unsigned char fldtype;/* type of field */
unsigned fldname[16];/* name of field */
unsigned foffset;/* offset into record */
unsigned length; /* length of field */
unsigned char decsig;/* significance for decimals */
unsigned char decdec;/* number of decimal places */
unsigned arrnum; /* array number */
unsigned picnum; /* picture number */
};
The «fldtype» field defines the field type.  The following
types are currently used.
1 — LONG
2 — REAL
3 — STRING
4 — STRING WITH PICTURE TOKEN
5 — BYTE
6 — SHORT
7 — GROUP
8 — DECIMAL
3) KEY AND INDEX DESCRIPTORS
If any keys are defined for the file, key descriptors follow field
descriptors.  Like the field descriptors, one key descriptor corresponds to
each key/index defined.  Key descriptors are divided into two parts:
keysects and keyparts.
They are defined below:
struct {
unsigned char numcomps;/* number of components for key */
char keynams[16]; /* name of this key */
unsigned char comptype;/* type of composite */
unsigned char complen;/* length of composite */
} KEYSECT;
struct {
unsigned char fldtype;/* type of field */
unsigned fldnum; /* field number */
unsigned elmoff; /* record offset of this element */
unsigned char elmlen;/* length of element */
} KEYPART;
There is exactly one KEYSECT for each key/index defined and one KEYPART for
each field component in a key or index.
4 & 5)PICTURE AND ARRAY DESCRIPTORS
Picture and array descriptors will be discussed later in the bulletin.
6) DATA
Each record of data is preceded by a header.  This header is defined as
follows:
struct {
unsigned char rhd;/* record header type and status */
unsigned long rptr;/* pointer for next deleted record or memo if active */
};
«rhd» is a bit field that tells Professional Developer the status
of the record:
bit 0 — new record
bit 1 — old record
bit 2 — revised record
bit 4 — deleted record
bit 6 — record held
The header is followed by fields that have been defined for  records.
The previous discussion contained quite a bit of information, but not every
section was explained completely.  To simplify matters, we will look at an
actual Clarion data file.  For this example, we will use a simple file.
Taken from the Professional Developer’s EXAMPLES subdirectory, it is a small
file called PHONEBK.  It is defined as follows:
FILE, PRE(PHN), RECLAIM, CREATE
PHN:BY_NAMEKEY(PHN:NAME),DUP,NOCASE
PHN:BY_COMPANYKEY(PHN:COMPANY),DUP,NOCASE
RECORD RECORD
NAME STRING(30)
COMPANY STRING(30)
ADDRESS STRING(30)
CITY STRING(28)
STATE STRING(2)
ZIP STRING(6)
PHONE STRING(11)
. .
To summarize the file, six fields are simple strings, deleted records are
reclaimed by the system, two keys are defined, and both keys are case-sensitive
and allow duplicates.  This file has no memo fields, is not encrypted, and
has no composite keys.  Now we will look at each section of the file. Since
this file only has two records, a dump of the entire file is shown below:
00000: 43 33 A0 00 02 02 00 00 00 00 00 00
00 07 00 00|C3…………..|
00010: 00 00 00 89 00 44 01 00 00 02 00 00 00 01 00 00|…..D……….|
00020: 00 00 00 00 00 52 45 43 4F 52 44 20 20 20 20 20|…..RECORD
|
00030: 20 20 20 20 20 20 20 20 20 20 20 20 20 50 48
4E|             PHN|
00040: 20 20 20 00 00 00 00 00 00 00 00 9B E4 4F 00 1C|   ……….O..|
00050: 0D 01 00 00 00 03 50 48 4E 3A 4E 41 4D 45 20 20|……PHN:NAME  |
00060: 20 20 20 20 20 20 00 00 1E 00 00 00 00 00 00
00|      ……….|
00070: 03 50 48 4E 3A 43 4F 4D 50 41 4E 59 20 20 20 20|.PHN:COMPANY
|
00080: 20 1E 00 1E 00 00 00 00 00 00 00 03 50 48 4E 3A| ………..PHN:|
00090: 41 44 44 52 45 53 53 20 20 20 20 20 3C 00 1E 00|ADDRESS
<…|
000A0: 00 00 00 00 00 00 03 50 48 4E 3A 43 49 54 59 20|…….PHN:CITY |
000B0: 20 20 20 20 20 20 20 5A 00 1C 00 00 00 00 00
00|       Z……..|
000C0: 00 03 50 48 4E 3A 53 54 41 54 45 20 20 20 20 20|..PHN:STATE
|
000D0: 20 20 76 00 02 00 00 00 00 00 00 00 03 50 48 4E|  v……….PHN|
000E0: 3A 5A 49 50 20 20 20 20 20 20 20 20 20 78 00 06|:ZIP
x..|
000F0: 00 00 00 00 00 00 00 08 50 48 4E 3A 50 48 4F 4E|……..PHN:PHON|
00100: 45 20 20 20 20 20 20 20 7E 00 06 00 0B 00 00 00|E
~…….|
00110: 00 00 01 50 48 4E 3A 42 59 5F 4E 41 4D 45 20 20|…PHN:BY_NAME  |
00120: 20 20 20 70 1E 03 01 00 00 00 1E 01 50 48 4E 3A|   p……..PHN:|
00130: 42 59 5F 43 4F 4D 50 41 4E 59 20 20 70 1E 03 02|BY_COMPANY  p…|
00140: 00 1E 00 1E 01 00 00 00 00 4D 61 72 6B 20 45 2E|………Mark E.|
00150: 20 44 61 76 69 64 73 6F 6E 20 20 20 20 20 20 20| Davidson
|
00160: 20 20 20 20 20 20 20 43 6C 61 72 69 6F 6E 20
53|       Clarion S|
00170: 6F 66 74 77 61 72 65 20 20 20 20 20 20 20 20 20|oftware
|
00180: 20 20 20 20 20 31 35 30 20 45 2E 20 53 61 6D 70|
150 E. Samp|
00190: 6C 65 20 52 6F 61 64 2C 20 53 75 69 74 65 20 32|le Road, Suite 2|
001A0: 30 30 20 50 6F 6D 70 61 6E 6F 20 42 65 61 63 68|00 Pompano Beach|
001B0: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
46|
F|
001C0: 4C 33 33 30 36 34 20 00 30 57 85 45 55 01 00 00|L33064 .OW.EU…|
001D0: 00 00 52 61 79 20 50 69 64 67 65 20 20 20 20 20|..Ray Pidge
|
001E0: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
20|
|
001F0: 50 72 6F 78 69 6D 69 74 79 20 54 65 63 68 6E 6F|Proximity Techno|
00200: 6C 6F 67 79 20 20 20 20 20 20 20 20 20 20 35 35|logy
35|
00210: 31 31 20 4E 45 20 32 32 6E 64 20 41 76 65 6E 75|11 NE 22nd Avenu|
00220: 65 20 20 20 20 20 20 20 20 20 20 20 46 6F 72 74|e
Fort|
00230: 20 4C 61 75 64 65 72 64 61 6C 65 20 20 20 20 20| Lauderdale
|
00240: 20 20 20 20 20 20 20 20 46 4C 33 33 30 36 33
20|        FL33063 |
00250: 00 30 55 66 35
11
|.OUf5. |
The first section is the file header.  Below is a break down of the
file header by field.  Preceding each field name is the offset in
hexadecimal for this file with the assumption that the file starts at offset
0000.  If you are using Scanner to view the file, add one to each offset as
Scanner starts the file at offset 0001.
(0000) filesig
The file’s signature (43 33).  Professional Developer Version 2 created the
file.  Since bytes are stored in a reverse order (due to the Intel
architecture), this value is actually hex 3343.
(0002) sfatr
The file’s attributes (A0 00).  This is a bit-field, so it is converted to
binary.  Hex 00A0 = 10100000 (the top byte is not used).  Bits 7 and 5
are turned on.  Referring to the discussion of the sfatr field, this
definition means «file may be created» and «reclaim deleted
records.»
(0004) numbkeys
The number of keys (02).  This file has two key fields.
(0005) numrecs
The number of records (02 00 00 00).  This file has two records.
(0009) numdels
The number of deleted records (00 00 00 00).  This file has no deleted
records.
(000D) numflds
The number of fields defined (07 00).  This file has a total of 7 fields.
(000F) numpics
The number of pictures (00 00).  Since the file has no pictures defined, it
does not have a picture description section.
(0011) numarrs
The number of arrays (00 00).  Since the file has no arrays defined, it
does not have an array description section.
(0013) reclen
The record length, including the header (89 00).  Hex 0089 equals 137
decimal.  Our fields use 132 bytes (30 + 30 + 30 + 28 + 2 + 6 + 6).
Clarion’s record header adds 5 bytes to this total.  Note: the DECIMAL(11,0)
field uses 6 bytes.
(0015) offset
The beginning of the data area (44 01 00 00).  The data begins 0144 hex
bytes into the file.

(0019) logeof
The logical end-of-file (02 00 00 00).  This file logically ends at record
2.
(001D) logbof
The logical beginning-of-file (01 00 00 00).  This file logically begins at
record 1.
(0021) freerec
The first usable deleted record (00 00 00 00).  Since the file has no
deleted records, this field is zero.
(0025) recnam
The record name without the prefix (52 45 43 4F 52 44 20 20 20 20 20 20 = «RECORD»).
The strings are padded with spaces and have no terminating NULL to indicate
end-of-string.
(0031) memnam
The memo name without the prefix (20 20 20 20 20 20 20 20 20 20 20 20).
Since no memo is defined for the file, this field is all blanks.
(003D) filpre
The file name prefix (50 48 4E = «PHN»).
(0040) recpre
The record prefix (20 20 20).  Since there is no record prefix, this field
is all blanks.
(0043) memolen
The size of the memo file (00 00).  Since the file has no memo field, this
field has a value of 0.
(0045) memowid
The column width of the memo file (00 00).
(0047) reserved1
This field is reserved (00 00 00 00).
(004B) chgtime
The time of the last change to the file (9B E4 4F 00) was 02:32 PM.  This
time is stored in an «absolute» format, and is explained below.
(004F) chgdate
The date of the last change to the file (1C 0D 01 00) was 08/11/89.  This
date is also stored in an «absolute» format.  See below.
(0053) reserved2
This field is also reserved (00 00).
Field descriptors are explained next.  There is one entry for each field
in the file.  Here is a breakdown of the first field descriptor:
(0055) fldtype
The field type (03).  This field type is a STRING.
(0056) fldname
The field name (50 48 4E 3A 4E 41 4D 45 20 20 20 20 20 20 20 20 = «PHN:NAME»).
This name includes the prefix and is padded with spaces.
(0066) foffset
The offset into each record for the field (00 00).  Since this is the first
field in each record, it has an affect of 0 (i.e. it is at the beginning of each
record).
(0068) length
The field length (1E 00).  This is the length of the field (in bytes).
Since hex 1E = 30 decimal, the field is 30 bytes long.
(006A) decsig
The number of decimal places in the field (00).  Since it is not a numeric
field, this field is 0.
(006B) decdec
The significance for decimals (00).  Since it is not a numeric field, this
field is 0.
(006C) arrnum
The array number for the field (00 00).  Since it is not defined as an
array, this field is 0.
(006E) picnum
The picture number for the field (00 00).  Since no picture number is
associated with the field, it is 0.
This entry is followed by fields 2 through 7.  These field are similar
to field 1 with the exception that the fldname, foffset and length fields may
have different values.  Field 7 is a DECIMAL, its fldtype is 08 and its
length is 6 bytes-which is its actual length in the file.
Key descriptors follow field descriptors.  As in field descriptors, only
one key descriptor for each key is defined for a file.  In this example,
there are two key descriptors.
Here is the breakdown of the first key descriptor (remember that key
descriptors are broken into two sections, KEYSECTs and KEYPARTs):
(0112) numcomps
The number of components for the key (01).  Since this key is not a
component key (it is composed of only one field), this field has a value of 1.
(0113) keynams
The key name (50 48 4E 3A 42 59 5F 4E 41 4D 45 20 20 20 20 20 = «PHN:BY_NAME»).
This is the name of the key, as defined by Professional Developer.  It is
padded with spaces.
(0123) comptyp
The type of the composite key (70).  This bitfield directs Professional
Developer that this is a key file (not an index file), duplicate entries are
allowed, and case does not matter in keys.  Since this field is only useful
when you are processing keys, it will be skipped.  For specific information,
see Clarion Technical Bulletin #118 on key/index files.
(0124) complen
The length of the composite key (1E).  The length of the key is Hex 1E = 30
decimal.
(0125) fldtype
The type of field this key is based on (03).  Since the field uses the same
values as the fldtype in the field descriptor, it is a STRING.
(0126) fldnum
The field number this key is based on (01 00).  This key is based on field
#1 in the file.
(0128) elmoff
The offset into the record for the field this key is based on (00 00).
Since the key is based on the first field in each record, the offset is 0.
(012A) elmlen
The length of the element in the file this key is based on (1E).
The descriptor for key number 2 follows the key descriptor.  It is
similar with the exception of the keynams, fldnum, and elmoff fields.
The data section is explained next.  However, Clarion’s file system has
added 5 bytes to the front of your records.  For this first record, the
header looks like this:
(0144) rhd
The record header type and status (01).  From the previous discussion of
the header, 01 (binary 00000001) means «new record.»  Generally,
do not be concerned with this field unless it is set to «deleted record»
or «locked record.»
(0145) rptr
The pointer to the memo (00 00 00 00).  Since the file has no memos, this
field is set to 0.  If this record were deleted, the field would point to
the next deleted record.
The record header for record 2 is exactly the same as record 1.  Each
field is stored with no separators between them.  STRINGs are padded with
spaces out to their maximum defined length.  DECIMALs are stored in a
packed BCD-like format, with two digits per byte.  Since the DECIMAL field
was defined as DECIMAL(11), it use 6 bytes (2 digits per byte, with a half byte
of padding because 11 is not divided into 2 evenly).  The DECIMAL file is
displayed for the first record: 00 30 57 85 45 55 = 305-785-4555.  Even
though it will fit in 5 bytes, use 6 bytes because it directed Clarion was told
it would use 11 digits.  However, since separators are removed in the
number, use a picture to display it correctly.
This is a fairly simple example to understand.  But, what about key
files?  Unfortunately, even simple key files are difficult to explain.
Clarion products use a modified version of a B+ tree, named after the creator, R.
Bayer.  Instead explaining the complexity of key files, we will cover the
basics.
KEY AND INDEX FILES
Key and Index files are read and written in blocks of 512 bytes.  A key
file has a 512 byte header that contains information about the key file
structure.  Following the header is a series of 512 byte nodes that allows
programs to process a data file in key order.  Although the key file header
occupies 512 bytes, currently only 35 bytes are used.
The header of a key is laid out below:
struct {
unsigned long root;/* number of root node */
unsigned long numkent;/* number of key entries */
unsigned long numnode;/* number of nodes for this index */
unsigned long lastnod;/* node number of last node */
unsigned long keyeof;/* record # of end of file */
unsigned long keybof;/* record # of beginning of file */
unsigned long unused;/* first unused node of file */
unsigned char keytyp;/* type of key */
unsigned char keynode;/* # of keys per node */
unsigned char numcmps;/* # of components of key */
unsigned keylen;/* total length of key entry */
unsigned numlvls;/* number of levels */
char cvoid[477];/* reserved space */
};
Do not attempt to change key files.  If you are interested in key files
layout, refer to Clarion Technical Bulletin #118 on key/index files.
Another difficult aspect of understanding key files is Clarion’s method of
storing the actual key.  Keys are kept in a truly «sortable»
order involving a lot of byte and bit flipping.  If you want to read a data
base in a certain order, use Sorter to sort the file before reading it.

DATES AND TIMES IN THE HEADER
As mentioned previously, dates and times in the header are stored in an «absolute»
format.  Below an absolute date and time (in Clarion) is converted to a
more easily readable number.  Given an absolute time (abstim):
IF abstime < 1 OR abstime > 8640000
THEN
STOP! abstime is invalid
ELSE
abstime = abstime — 1
hour = abstime / 360000
abstime = abstime % 360000
minute = abstime / 6000
abstime = abstime % 6000
seconds = abstime / 100
hundreds = abstime % 100
.
The above algorithm assumes integer arithmetic; abstime is stored in a
«long integer» field, that occupies 4 bytes.
Now we will take the time from the header.  The «time of last
change» for the file is hex 9B E4 4F 00.  Since the order of the bytes
must be reversed (bytes are stored low-byte, then high byte; in a long integer,
the bytes are also flipped), the hex value is 00 4F E4 9B.  Follow these
steps:
1.Use the hex value 00 4F E4 9B.
2.Convert it to decimal.  The value is 5235867.
3.Divide 5235867 / 360000 = 14 with a remainder of 195867.  14 is the hour.
4.Divide 195867 / 6000 = 32 with a remainder of 3867.  32 is the minute.
5.Divide 3867 / 100 = 38 with a remainder of 67.  38 is the seconds and 67
is the hundredths of seconds.
The time is 14:32:38.67 or 2:32 PM as the last time change for the file.
Dates are more involved.  Given an absolute date (absday):
IF absday <= 3 OR absday > 109211
THEN
STOP! absday is invalid
ELSE
IF absday > 36527
THEN
absday = absday — 3
ELSE
absday = absday — 4
.
year = (1801 + (4* (absday / 1461)))
absday = absday % 1461
IF absday != 1460
THEN
year = year + (absday / 365)
day = absday % 365
ELSE
year = year + 3
day = 365
.
IF year < 100
THEN
year = year + 1900
.
IF year % 4 = 0 AND year != 1900
THEN
number_of_days_in_February = 29
ELSE
number_of_days_in_February = 28
.
LOOP i = 1 TO 12 BY 1
day = day — number_of_days_in_month_i
IF day < 0
THEN
day = day + number_of_days_in_month_i + 1
BREAK
.
month = i
end if
The ‘number_of_days_in_month_i’ is a nonsense variable equal to the number of
days in a particular month i, with i running from 1 (January) to 12 (December).
Let us review the file header example.  The date for this file is a
«long integer,» stored on disk as hexadecimal 1C 0D 01 00.  When
the byte order is reversed, the hex value is 00 01 0D 1C.  Follow these
steps:
1.Use the hex value 00 01 0D 1C.
2.Convert it to decimal. The value is 68892.
3.Since 68892 is > 36527, subtract 3. The value is 68889.
4.Compute (1801 + (4 * (68889 / 1461))).  The result is 1989.  1989 is
the year.
5.Compute 68889 % 1461.  The result is 222.
6.Since 222 is != 1460, compute 222 / 365 and the result is 0.  Add 0 to
1989 (the year).
7.Compute 222 % 365. The result, 222, is the day.
8.Since 1989 % 4 is != 0, the date is not in a leap year and it means that
February has 28 days.
9.Start the loop from 1 to 12, subtracting the number of days in each month from
‘day’ until ‘day’ drops below 0 (the value of i in the far right column is the
current value of the loop variable).
January: day = day — 31, or 191 (i = 1).
February: day = day — 28, or 163 (i = 2).
March: day = day — 31, or 132 (i = 3).
April: day = day — 30, or 102 (i = 4).
May: day = day — 31, or 71 (i = 5).
June: day = day — 30, or 41 (i = 6).
July: day = day — 31, or 10 (i = 7).
August: day = day — 31, or -21 (i = 8).
10.Compute day = day + 31 + 1. The result is 11.  This is the actual day
of the month.  Since the loop terminated at i = 8, we know that 8 is the
month number.  As a result, our date is 08/11/89.
To simplify the discussion, composite keys, picture descriptors, array
descriptors, and memo fields were not included.  We will look at each
section to see how they affect the file layout.
COMPOSITE KEYS
As discussed previously, key descriptors are made up of two parts: KEYSECTs and
KEYPARTs.  The file example had both.  When a composite key is used,
only one KEYSECT is in the key descriptors, but there is one KEYPART for each
field that makes up the component key.  For example, if you have a key
composed of the PHN:NAME and PHN:COMPANY fields, your key descriptor would have
three elements (instead of two).  You still have a KEYSECT, but the «numcomps»
field has a value of 2.
The «complen» field is equal to the sum of the lengths of the
PHN:NAME and PHN:COMPANY fields.
Next, you have two KEYPARTs — one for the NAME field and one for the COMPANY
field.  The only difficulty with handling composite keys is that in order
to build your key, you must read more than one field from each record.
PICTURE DESCRIPTORS
If a picture is assigned to a field in your database, then a picture descriptor
is created for the field.  Picture descriptors follow the key descriptors
in your file.  The following is an example of their layout:
struct {
unsigned piclen;
char    picstr[256];
};
Since «picstr» (like other strings in data files) is not
null-terminated, «piclen» contains the actual length of the picture.
If a picture descriptor has been created, the number associated with it is
placed in the «picnum» field of the field descriptor associated with
the picture.  Picture descriptor numbering starts at 1.  If you are
reading the field descriptors and the «picnum» field is not zero,
remember to search for the picture descriptor,  because it takes space in
the file before the actual data records occur.
ARRAY DESCRIPTORS
struct {
unsigned numdim;/* dims for current field */
unsigned totdim;/* total number of dims for field */
unsigned elmsiz;/* total size of current field */
struct {
unsigned maxdim;/* number of dims for array part */
unsigned lendim;/* length of field */
} ARRPART[sizeof(ARRPART)*totdim];
} ARRDESC;
Array descriptors are not as complicated as they appear.  Each array
descriptor consists of the «numdim,» «totdim,» and the
«elmsiz» fields, followed by one or more «ARRPART»
structures.  There is one «ARRPART» for each dimension in the
array.
Let use a simple case.   A single array causes one array descriptor
to be created.  Assuming the allocation STRING(10),DIM(3), the following
array descriptor would be created:
struct {
unsigned numdim = 1;
unsigned totdim = 1;
unsigned elmsiz = 30;
struct {
unsigned maxdim = 3;
unsigned lendim = 10;
};
};
«numdim» and «totdim» explain if the descriptor is part
of another array.  If they are equal, the array descriptor stands alone.
«elmsiz» is the total size of the elements of this array, «maxdim»
is the highest value allowed as the dimension, and «lendim» is the
length of each dimension.  This array has 3 «elements» that use
10 bytes, giving an element size of 30.  Since this array only has one
dimension (3), there is only one ARRPART structure   Now let us add a
group specifier:
GROUP,DIM(5)
STRING(10),DIM(3)
Two array descriptors are created; one for the group and one for the array.
Because the group has a dimension, an array descriptor is created for the group.
The array descriptor for the group will look like this:
struct {
unsigned numdim = 1;
unsigned totdim = 1;
unsigned elmsiz = 150;   /*5 * 30 */
struct {
unsigned maxdim = 5;
unsigned lendim = 30;
};
};
Since the group has one dimension (5) with each element using 30 bytes, the
total length is 150 bytes.  This is a second array descriptor for the
string:
struct {
unsigned numdim = 1;
unsigned totdim = 2;
unsigned elmsiz = 30;
struct {
unsigned maxdim = 5;
unsigned lendim = 30;
};
struct {
unsigned maxdim = 3;
unsigned lendim = 10;
};
};
This array descriptor has two ARRPARTs; one covers the array of 5 (the group)
while the second one covers the array of strings.  Let us make the example
more complicated:
GROUP,DIM(5)
STRINGS(10),DIM(3,6)
We still have only two array descriptors.  Again, the first one is for
the group:
struct {
unsigned numdim = 1;
unsigned totdim = 1;
unsigned elmsiz = 900;/* 3 * 6 * 10 * 5 */
struct {
unsigned maxdim = 5;
unsigned lendim = 180;/* 3 * 6 * 10 */
};
};
Now the array descriptor for the strings:
struct {
unsigned numdim = 2;
unsigned totdim = 3;
unsigned elmsiz = 180; /* 3 * 6 * 10 */
struct {
unsigned maxdim = 5;
unsigned lendim = 180;
};
struct {
unsigned maxdim = 3;
unsigned lendim — 60;
};
struct };
unsigned maxdim = 6;
unsigned lendim — 10;
};
};
Array descriptors, like picture descriptors, are numbered.  The array
descriptors occur following the picture descriptors.  If an array
descriptor has been created for a field, the number assigned to it will be in
the «arrnum» field of the corresponding field descriptor.

MEMO FIELDS
Memo fields are unique because they are stored in a separate file from your data.
If there is a memo file associated with a data base, the «memnam»
field of the data base header has name of the memo field and «memolen»
and «memowid» fields have the length and width of the memo field.
If a record in the data base has a memo associated with it, the «rptr»
field in the header, occurring prior to each record, will have a value.
With this value, calculate the offset needed to go into the memo file to read
the memo.
Given «rptr», the following formula yields the offset:
offset — ((rptr — 1) * 256) + 6
The memo file consists of a 6 byte header, followed by 256 byte memo blocks.
The header appears below:
struct {
unsigned memsig = 0x334D;    /* memo file signature */
unsigned long firstdel;
/* first deleted memo block */
};
Memo blocks appear below:
struct {
unsigned  long nxtblk;     /* next block for this memo
*/
char         memo[252];
/* memo text */
};
Read the memo file in 256 byte chunks, take the value of «rptr» and
calculate the offset into the memo file.  Go to the offset and read 256
bytes.  The first four bytes will either point to the next 256 byte chunk
or be zero, indicating that there are no more blocks for the memo.  The
remaining 252 bytes are plain text.
CONCLUSION
Using this technical bulletin, you should be able to read most any data base,
unless it is encrypted or compressed.  This format contains many parts that
serve a specific purpose to help you accomplish your goals.