# MacProject Pro 1.5 file format

How MacProject Pro 1.5 stores a project (file type `MPRX`, creator `MPRP`) and a
Perspective (`MPRE`), as worked out for Glass Project. `MPXKit` reads and writes it
(`Container`, `Decoders`, `Encoders`, `WriterSync`).

Everything here came from reverse engineering, for interoperability: reading the
application's code and resources, decoding the 31 project files that ship with it,
and checking results in MacProject Pro 1.5 itself, running under emulation. Names
like `CReadWrite_1844` are labels for functions in the original's code, given so a
finding can be traced to where it was read. Marked *guess* where a meaning is
inferred rather than confirmed.

See also [scheduling.md](https://msitarzewski.github.io/GlassProject/scheduling.html.md) (how dates are calculated),
[leveling.md](https://msitarzewski.github.io/GlassProject/leveling.html.md) (the resource leveler) and [behavior.md](https://msitarzewski.github.io/GlassProject/behavior.html.md)
(menus, shortcuts and gestures).

## Field type codes (`CFileAccess_2a40`)

Most codes read or write a fixed byte count through the stream's vtable
method at +0x18. Stream object: +0 creator, +4 file type, +8 file version
(short), +0xa format class, +0xb mode (1 when reading), +0xc vtable. All
confirmed from the code and against the samples unless marked.

| Code | On disk |
|---|---|
| 0 | Handle: length (u32 from version 0x13b, else u16), then |length| bytes |
| 1 | 10: SANE 80-bit extended (guess from size) |
| 2, 3, 8, 0x14, 0x19, 0x1b, 0x20 | 4. 2 is IEEE float32 in every sample field (costs, rates, counts, work: 87.5, 1/48, 140000) except `shap` +0x94 (bit patterns) and the `cbkt` amounts (below). 8 is an object reference {u16 kind, u16 index}; index 0xFFFF = none, 0xFFFFFFFF = null (all 24,314 sample values fit: 340 null, the rest known kinds). 0x1b holds durations in seconds (0x31380 = 56 h) |
| 4, 9, 0x17, 0x1d | 2 (short) |
| 5, 0x12 | 1 (byte / Boolean) |
| 6 | String kept in the data manager: s16 length, then bytes when length > 0 |
| 7, 0x15, 0x1e | 8. 0x15 holds two longs in tasks (chart position, guess) |
| 0x0a–0x11 | one Boolean byte per flag bit 0x80…0x01, packed into one byte in memory |
| 0x13 | Pascal string: s16 length, then (length & 0xff) bytes |
| 0x16 | 16 (four longs in tasks: a rect, guess) |
| 0x18 | Field id (short). In Perspective files (version > 0x141) an id > 0xfff (user field) is followed by its name (code 6) and data type (short) |
| 0x1a | Date: u32 seconds since 1904-01-01, truncated to whole minutes. 0 = none, 0xFFFFFFFF special, nonzero values below 0x80000000 (before 1972) clamped to 0x80000000 |
| 0x1c | 6: RGBColor (u16 red, green, blue) |
| 0x1f | Object kind as its 4-char tag (`FUN_001433a8`) |
| 0x21 | 256: Str255 |
| 0x22 | 64: Str63 |

Composite encodings used by the serializers:
- Object list (`CFileAccess_3044`): repeat {u8 end flag; if 0: kind tag, u16
  index, that kind's body}. Tasks nest `idep`, `iall`, `uatt` this way
  (`note` too per `FUN_00143acc`, but no sample does); Perspective files
  nest `srch`.
- Counted array (`CFileAccess_2d34`, `CFileAccess_2e50`, `CArray_3690`): s16
  count, then that many items.
- Optional item (`CFileAccess_2c78`): presence byte, then the item.
- Attribute set (`FUN_000bf118`): kind tag (`atst`) + counted {field id,
  object ref [, text style when the ref is to an `srun`]}.
- Text style (`FUN_000bf214`): kind tag, then a counted run list for `srun`
  or an inline `txst` body.
- Value whose type depends on a field id (`srch`, `row `): data type from the
  built-in field table (byte 1 of each 4-byte entry at 0x8d004, ids 0–0x73)
  or, for user fields (id > 0xfff), from the `attr` record; data type →
  stream code via `CFileAccess_0e30`.

## Project file format (type `MPRX`, data fork; `MPRE` = Perspective)

Creator `MPRP`. `FUN_00142df0` reads the u16 version at offset 0; a version
from 301 to 329 (0x12d–0x149) is the current format (class 4). New streams
start at version 0x149 (`FUN_00142cfc`; guess: the version 1.5v1 writes);
the samples are 0x148, two are 0x149. Other
types and versions (`MPRD`, `MPRC`, `MPRH`, versions 0xd2–0xd6, 0x61, or a
0x1e61 marker at 0xa2) select older format classes 1–3, presumably
MacProject II files (guess); those paths are not decoded.

- `0x000` u16 version
- `0x002` 27 list slots × {u16 count, u32 head, u32 tail} (26 slots before
  version 0x139). Slot → kind (`FUN_00141a04`): 0 `font`, 1 `txst`, 2 `attr`,
  3 `anam`, 4 `srch`, 5 `dcst`, 6 `cal `, 7 `scop`, 8 `rsrc`, 9 `shap`,
  10 `outl`, 11 `task`, 12 `note`, 13 `subp`, 14 `supr`, 15 `prsp`, 16 `extk`,
  17 `edoc`, 18 `cbkt`, 19 kind 0x80 (no tag), 20 `note`, 21 `pbsb`,
  22 `smry`, 23 `olvl`, 24 `row `, 25 `cary`, 26 `task` (a second order over
  the same task records). The app reads slots 0–25 and follows `next` links
  until 0; it never reads the counts.
- `0x110` u16 (zero) in projects; optional unlinked `MPBn` blocks (tag, u16,
  u16 index, u16 flags, u32 length, payload) follow in files with
  subprojects (10 blocks, in Product Development Plan and Prototype
  Subproject). The read path for them is not found yet.
- then records, back to back to EOF. Header (`FUN_00142a90`): u32 next, u32
  prev; lists 11 and 16 add a second u32 pair (for tasks, the list 26
  links); then the kind tag and a u16 index. The body follows.
- Records are byte-packed (odd offsets are normal).

Perspective files (`MPRE`) use the same header but only list 15: one `prsp`
record at 0x110 (no gap), and the header count is not the record count
(8 in every sample). `CFileAccess_3258` reads only slot 15 for `MPRE`.

### Record bodies

`MPXKit`'s `Decoders` implement every body below field by field, with labels
that are the field's offset in the in-memory object, so each one maps back to
the original's serializer. Field meanings are mostly not decoded
yet. Kind → class → serializer, from `CConstruct_0540` and the vtables:

| Tag | Constructor | Vtable | Serializer (vptr+0x2c) | Notes |
|---|---|---|---|---|
| `outl` | `CConstruct_18d0` | ffffdf18 | `CReadWrite_59e4` | pre-read `CReadWrite_6068` (vptr+0x84); base `CReadWrite_0bd8` |
| `smry` | `CConstruct_1b30` | ffffdfa4 | `CReadWrite_60bc` | = `59e4` + more |
| `task` | `CConstruct_1bc8` | ffffe034 | `CReadWrite_6348` | = `60bc` + more; pre-read `CReadWrite_6780`. A class short (vptr+0x78) is stored and the object is re-classed from it on read (`CTask_0824`); Supertasks (Type 6) add a trailing string |
| `extk` | `CConstruct_0d1c` | ffffc808 | `CReadWrite_2654` | = task + document info; Type 7 (External) |
| `olvl` | `CConstruct_1d4c` | ffffe0c8 | `CReadWrite_6954` | |
| `note` | `CConstruct_0fd8` | ffffcbe4 | `CReadWrite_3ef0` | base `CReadWrite_3da8`: subtype short picks the value's type |
| `uatt` | `CConstruct_109c` | ffffcc58 | `CReadWrite_3ff0` | nested only |
| `idep` | `CConstruct_09a8` | ffffc370 | `CReadWrite_1120` | nested only |
| `iall` | `CConstruct_0120` | ffffaecc | `CReadWrite_008c` | nested only; leads with its own total length (version > 0x146) |
| `cal ` | `CConstruct_03d4` | ffffb95c | `CReadWrite_0630` | 7 weekday records, holidays; `CReadWrite_0584` variant when the data manager says so |
| `rsrc` | `CConstruct_13c0` | ffffd69c | `CReadWrite_4b08` | `CReadWrite_4a84` variant likewise |
| `subp` / `supr` / `scop` / `edoc` | `CConstruct_0aa0` / `0bb8` / `0c68` / `0db8` | ffffc760 / c798 / c7d0 / c914 | `CReadWrite_2460` / `254c` / `25a4` / `2778` | all start with document info (`FUN_000bc230`: two Str63 names, …) |
| `attr` | `CConstruct_0218` | ffffb470 | `CReadWrite_046c` | user field: id, data type, … |
| `anam` | `CConstruct_0298` | ffffb4a8 | `CReadWrite_04cc` | field id, name, short |
| `prsp` | `CConstruct_1168` | ffffd038 | `CReadWrite_4528` | |
| `shap` | `CConstruct_1588` | ffffd910 | `CReadWrite_53c0` | inline `mark` (`FUN_000beb70`) and `bar ` (`FUN_000becc8`) items |
| `srch` | `CArrayConstructor_0884` | ffffcfb4 | `CReadWrite_436c` | |
| `row ` | `CArrayConstructor_0974` | ffffd6d4 | `CReadWrite_4e28` | |
| `txst` | `CRes_3750` | ffffdbcc | `CReadWrite_55b0` | font ref, face, size, RGBColor |
| `font` | `CConstruct_1804` | ffffdc04 | `CReadWrite_5608` | font name (code 0x13) |
| `cbkt` | `CCost_0c98` | ffffc068 | `CReadWrite_1058` | |
| `dcst` | (document data manager) | ffffc438 | `CReadWrite_1844` (DM vptr+0x40) | pre-read `CReadWrite_15b8` (DM vptr+0x4c), then `CReadWrite_1254` |
| `cary` | (document data manager) | ffffc438 | `CReadWrite_1f2c` (DM vptr+0x28) | counted 4-byte refs |

`hday`, `srun`, `atst`, `bar `, `mark` have no serializer of their own (the
base `CReadWrite_1118` is empty); they are written inline by their owners.
`pbsb`, `txt ` and `proj` have no case in `CConstruct_0540`; not seen in
the samples.

Validation (`SampleTests`): all 31 files (23 projects, 8 Perspectives),
5,016 records, every body decoded to its exact length; all 902 nested
`iall` length fields agree. `edoc`, `extk`, `row ` and `scop` never occur
in the samples, so their layouts come from the code only.

Two samples carry damaged tags inside a task's object list:
`Conference Planning` ('ia&&' in the task at 0x33c7) and `Fund-raising
Campaign` ('Iall' in the task at 0x3d7d). The bodies are `iall` ones (their
length fields agree), and `MPXKit` reads them as such and reports it. The
app would map these tags to whatever is left in D7 (low byte 1 in
`CFileAccess_3044`), so it may fail to open these two files; not yet tried
in MacProject Pro itself.

Field encodings seen so far:
- Strings: code 6 (s16 length + MacRoman bytes) for task, resource and field
  names; 0x13 for font names; Str63 inside document info.
- Dates: code 0x1a. `0xA3A58180` (1991-01-01 00:00) appears as a
  base/placeholder date.
- Durations: code 0x1b, seconds of work time (0x7080 = 8 h day, 0x23280 = 40 h week).
- Quantities: IEEE float32 in code 2 fields (`3F800000` = 1.0).
- Cost buckets (`cbkt`) keep 80-bit SANE extended amounts in memory
  (`CDataObject_0b64` copies 10 bytes), but `CReadWrite_1058` writes them
  with code 2, so the file holds only the first 4 bytes: sign/exponent and
  16 mantissa bits including the explicit integer bit (`40 10 88 b8` =
  140,000). Bucket totals match the tasks' costs (Magazine Publishing:
  131,848 vs 131,850 planned cost, 140,000 vs 140,000 income).
- `txst` font field 0x8001 (guess: high bit = index into the `font` records).

### Field names

Sources, all confirmed against each other:
- The built-in field table, 4 bytes per field id 0–0x73 at 0x8d004 (in
  CParser): flags, data type, u16 offset in the object. Accessors next to it:
  0x8d1d4 offset, 0x8d1e6 data type, 0x8d1f8 flags & 0x30, 0x8d20e flags &
  0x40, 0x8d224 flags & 0x0f. Offset 0 means a computed field. Flags 0x40
  look like "user-editable" (Actual Start, Fixed Cost, Task ID have it;
  Earliest Start, Slack, Resource Cost don't) — guess. The low nibble picks a
  related object: 3 for the calendar (Work Days/Hours per Week), 4 and 5 for
  a dependency's predecessor and successor (From/To Name, From/To Task ID).
- Names: `ATNM` 128 (114 default names), 129 (user fields 0x1001
  "Subtitle", 0x1002 "Attached Document") and 130 (0x54 "Note"). Documents
  copy them into `anam` records; all 23 projects carry the same names (six
  use older wording, e.g. "Type" for "Lag Type"). Ids 0x19, 0x4a, 0x4b have
  none.
- Per-class field getters, vtable slot +0x8 (returns a pointer to the
  field's value): `CDataObject_45cc` (outline row), `_53dc` (summary),
  `_5738` (task), `_2d1c` (dependency), `_0500`/`_0000` (allocation),
  `_3938` (resource), `_0a0c` (calendar), `_5dec` (outline level), `_0b64`
  (cost bucket), `_3560` (note). They agree with the table offsets and add
  the references.
- Type values (field 0x16, the class constant at vptr+0x78, stored as the
  outline/task "class" short), from STR# 11131: 0 Outline, 1 Custom
  Summary, 2 Outline Summary, 3 Palette (`outl`/`smry`), 4 Task,
  5 Milestone, 6 Supertask (`task`), 7 External (`extk`). The trailing
  string in `CReadWrite_6348` belongs to Supertasks.

Field names: the file's `anam` names win, and the application's built-in
names (its `ATNM` resources, `MPXKit`'s `defaultFieldNames`) are the
fallback. References are written `tag#index`.

Structure found from the getters and checked in the samples:
- Task (`outl`/`smry`/`task`): +0x3c parent, +0x40 first child, +0x44 next
  sibling, +0x48 previous sibling (all 604 sibling links are mutual; a
  first child never has +0x48). +0xcc allocations (each `iall` +0xa points
  back), +0x34 user-field values (`uatt` +0x34 = owner, +0x3c = field id),
  +0xd8 dependencies where the task is the predecessor, +0xdc where it is
  the successor (785 dependencies, each stored in both tasks), +0x30
  Timeline shape (else the document's default per class, `dcst`
  +0xda/+0xde/+0xe2/+0xe6), +0xd4 calendar, +0xe0 subproject, +0x38 note,
  +0x50 text style, +0x4c per-field text styles (`atst` items always point
  at `txst`), +0xfc / +0xfe the allocation whose duration overrides
  Duration / Actual Duration, +0xba the task's own actual duration (field
  0x19).
- Dependency: +0x2c predecessor, +0x28 successor (from the getter: From
  Name reads +0x2c), +0x38 calendar, +0x44 Lag, +0x4a Lag Type.
- Allocation: +0x6 resource, +0xa task, +0x4c bit 0x10 Driving Resource.
- Resource: +0x12 calendar. Outline level: +0x6 Level Name.
- Document (`dcst`) +0xd2 is the project summary block (Tasks, Milestones,
  Done Tasks, dates, costs, counts); it matches the record counts (Business
  Start-up: 39 tasks + 3 milestones = 42 `task`, 4 `smry`, 3 `rsrc`, 1 `cal `).

Settings, from the dialogs. Each dialog is a MacApp `view` resource (view id,
class, title); the dialog code stores each control's
value, by view id, into a document or Perspective field or flag bit:
- Preferences (`CPreferencesDlg`, views 3400–3408) → `dcst`: Document panel
  (Track Tasks in Open Charts, Page Breaks in Schedule Chart, Date and
  Duration Icons, Marks in Non-editable Table Cells, Non-scrolling Divider,
  Save Consolidated Master Dates; Default Calendar = +0xd6, Measurement =
  +0x228), Critical Path (threshold +0x230: 0 most critical tasks, 1 most
  critical path, 2 custom; custom days +0x21c; Show/Color/Size/Font/Style
  bits; its text style is a `txst` whose index is at +0xf6), Resources (the
  leveling and gathering options), Clean-Up (Height/Width/Align to Grid;
  +0x204 is the default task size (height, width) and +0x208 the grid
  spacing (v, h) in pixels: (50, 86) and (10, 10) show as .69 × 1.19 in.
  and .14 in. in MacProject Pro itself), Timeline display (Slack/Planned/Actual/Baseline,
  Task Names, Sweep Actuals), Outline (Roll Up Costs, Line Labels,
  numbering at +0x10e: 0 numbers, 1 bullets, 3 inherited). Exact bits are in
  `MPXKit`'s settings import (`Project.swift`).
- Formats and Number panels → the 8 bytes at `dcst` +0x8c: date order and
  format (popup indexes), date separator, flags (0x80 year, 0x40 month,
  0x20 day, 0x10 time shown; 0x08 set = cents hidden), decimal and
  thousands separators, then ':' (guess: time separator).
- Perspectives (`CPerspectiveDlg`, views 4000/4005): the ten "Remember In
  This Perspective" boxes are `prsp` +0x166/+0x167 bits (defaults match
  `CConstruct_1168`). +0xa2 is the Perspective's name, +0x114 its search
  formula (nested `srch`, e.g. Type = "Milestone"), +0x118 its sort order.
- Column layouts: lists of {field id, width, shown}. The document has eight
  (+0x138…+0x154), Perspectives twelve (+0x11c…+0x148, by chart number).
  Named by their columns: chart 1 Outline, 2 Resource Timeline, 3 Task
  Timeline, 4 Resource Table, 5 Project Table, 7 Allocation Table, 8 Cash
  Flow Table, 9 Dependency Table (charts 0, 6, 10, 11 have none; guess
  Schedule Chart, Resource Histogram, Project Overview). The Chart menu
  (MENU 4) lists the same charts.
- Chart windows (`dcst` +0x168, `prsp` +0x14c): per chart number, a window
  rect and a zoom percent (code 7, an IEEE double: 100.0 in every sample).
- Page setup (`dcst` +0x16c, `prsp` +0x150, `FUN_000ba774`): margins (code
  0x1e; 54 = 0.75 in. at 72 dpi, guess), a 120-byte `TPrint` print record
  (code 0 handle), then two optional panes, header and footer (order a
  guess; the first holds header text).
- File references (`FUN_000bc230`, in `subp`/`supr`/`scop`/`edoc`/`extk`/
  `prsp`): file name (+0x0), volume name (+0x44), directory ID (+0x84):
  e.g. "Prototype Subproject" on "King’sCourt". A subproject's +0x98 is its
  supertask in the master, and its four shorts +0x9c–+0xa2 are the indexes
  of its `MPB1`–`MPB4` blocks (128/129, or −1 when absent; they match the
  blocks in Product Development Plan and Prototype Subproject). Calendars
  (`cal ` +0x14) reuse the block, but there it holds a calendar name
  ("Project Calendar", "Editor's Calendar") and an empty volume; +0xa6 is
  usually the same name. +0xa6 is the name the app shows (Preferences and
  Task Info show "Default Calendar" where +0x14 holds "Project Calendar").

Checked in MacProject Pro itself (2026-10-17, a copy of Business Start-up):
- Preferences: every control on the Document, Critical Path, Resources,
  Clean Up, Timeline, Outline, Date and Number panels shows what the decoded
  fields and bits say (e.g. the critical path is drawn bold red: `txst` #2,
  face 1, size 9, color 0xDDDD).
- Task Info ("Idea Generation and Refining"): Task Duration 6 weeks (+0x74 =
  864,000 s), Calendar Name, % Done 100, Leveling Priority 100, Outline
  Number 2.1; its allocation Owner (`rsrc` #2), Work 6, Number 1.00, %
  Effort 100, Duration 6; no constraints; costs 0.00; Actual Start/Finish
  1/1/93 and 2/11/93; custom field Subtitle.
- Schedule Chart: a task's Location (+0x8) is the box's top-left (v, h) and
  Size (+0x10) its (height, width) in chart pixels: 50 × 86, the default
  task size; milestones 50 × 80. +0x18 (239, 181, 339, 364 for this task)
  looks like the box plus its date labels (guess).
- Schedule Chart drawing (Product Development Plan, Business Start-up,
  Layout ▸ Display Attributes):
  - `dcst` "fields" (16 field ids) are the Display Attributes: four slots
    per corner, in the order top-left, top-right, bottom-left, bottom-right.
    The first slot of each corner sits next to the box, and the next one
    stacks away from it. ES = slot 0, EF = slot 4, Duration = slot 12.
    Left-corner text ends about 8 px inside the box's left edge; right-corner
    text starts about 8 px inside its right edge. Labels have an opaque
    white background. A "Resource" slot shows an allocated resource
    (guess: the next one per slot, as Conference Planning's two Resource
    slots suggest).
  - Boxes are white with a 2 px shadow. Critical boxes have a 2 px frame.
    The name is top-aligned and centred. The dot fill marks 100% done
    tasks, not critical ones: "Research Market and Industry" is critical,
    50% done, and plain.
  - Notes: +0x18 is the note's absolute bounds on the chart. Attached notes
    (+0x36 set; +0x8 is relative) are drawn there, filled with +0x60
    (gray 0xDDDD in Product Development Plan), with a black frame and a
    turned-down bottom-left corner. Free text notes are white and unframed.
    The +0x60 yellow (0xFFFF 0xFFFF 0x6666) is not drawn for them. A line
    whose top falls inside the note draws even if it overruns the bottom.
  - +0x34 = 1 marks a legend (Layout ▸ Change to Legend). Its picture (a
    sample box) is drawn at +0x8/+0x10, and the Display Attributes field
    names are set around it in bold. Its +0x18 bounds include those labels.
  - Pictures are stretched to +0x8/+0x10. The coloured phase bands in
    Product Development Plan are 39 × 480 pictures (a 50% dither) stretched
    up to 40 × 3809. Their patterns stay 8 × 8 screen pixels, as QuickDraw
    patterns do.
  - Notes are drawn behind dependency lines and boxes. The first note in
    the list is frontmost (guess: the phase labels, notes 0–3, sit on the
    bands, notes 8–14). Attached notes sit above the other notes: the
    callout, note 17, which would otherwise be first drawn, shows on the
    yellow band.
- Perspectives: +0x9e is the View-menu command key (⌘1…⌘7 for the seven in
  Business Start-up). +0xa0 runs −2058…−2064 across them (guess: the menu
  command number). Edit Perspective shows the Remember boxes as decoded
  (Budget Reports: Search Formula off, the other nine on), and its search
  formula popup shows the referenced `srch` record's name.
- Search formulas (`srch` terms): +2 the field, +4 the value, +0 the
  comparison, +0x11 how the term joins the next. Business Start-up's four
  searches (Milestones, Completed, In Progress, Critical) find exactly what
  their names say with: comparison 0 = is, 2 = less than, 4 = greater
  than; join 1 = And (`QueryTests`). Guesses: 1 = is not, 3/5 the
  or-equal forms, 6 = contains, join 2 = Or, and +0xe set meaning the
  value is another field.
- A `txst` font of 0x8001 shows as Geneva, `font` record #0 (guess:
  0x8000 + a 1-based index into the `font` records).
- Durations show in weeks in this document ("Work-Weeks", the custom
  threshold's "Weeks"). The unit is `dcst` +0x10a, set by Chart ▸ Duration
  Scale (`CDurationScaleDlg`, view 18000): 1 minute, 2 hour, 3 day, 4 week,
  5 month in radio order; Business Start-up has 4, most samples 3.

Not named yet: calendar internals (weekday table, flags), note flags other
than +0x34/+0x36, shape and mark details, some `dcst`/`prsp` fields (the date-display
block at +0x94/+0x158 is a guess from `CReadWrite_50cc`), and in tasks
+0x18, +0x9a–+0x9e, +0xa3/+0xa6/+0xa7, +0xa8 flags, +0xc8/+0xca, most
+0x106/+0x107 flags, and the dates +0xe4…+0xf8. Those dates look like the
scheduler's working values: across all 619 sample tasks +0xf4 equals
Earliest Start and +0xf0 Earliest Finish, +0xf8 equals Actual Finish, +0xec
equals Earliest Start except on start milestones with a start constraint
(where it keeps an older date), and +0xe4/+0xe8 hold placeholders (1987–
1991-01-01) or older dates. Searching code by offset doesn't settle them:
`CAnalysis` uses the same offsets on its own node structure (task pointer at
node +0xde), and so does the MPX importer. The `aete` doesn't help: it
exposes tables whose columns are these same attributes, by `anam` name.

`SampleTests` decodes every sample and checks that each body is read to its
last byte; `WriterTests` writes each one back byte for byte.

---
Source: https://msitarzewski.github.io/GlassProject/file-format.html · How MacProject Pro 1.5 stores a project, record by record, as worked out for Glass Project.
