DDirectorWikiDirector & Lingo Encyclopedia

Native Lingo Internals

The recovered Dirapi.dll scripting machinery - symbol interning, the API registry, property descriptor records, executor branch families, and native error codes.

Overview

Inside Dirapi.dll, the scripting surface (the ticks, the locH of sprite 1, the font of member 1...) is not a hardcoded switch statement. It is a generic descriptor machine:

text
symbol string  ->  interned symbol ID  ->  registry lookup  ->  descriptor record
                                                        ->  shared descriptor executor
                                                        ->  emitted VM operations

Everything below is native code evidence unless noted (see evidence levels).

Symbol bootstrap and interning

Two string tables seed the symbol space at startup:

  1. A fixed bootstrap table at 68148894 (core names: mouseUp = ID 0x39, sprite = 0x2e, member = 0x58, chunk names char/word/item/line = 0x20-0x23, ...).
  2. A caller-provided Director term table at 68140e04 (loaded second: movie, puppet, ink, foreColor, ...).

The interning helper (FUN_680c1f63) hashes each length-prefixed string; if the name already exists it returns the existing ID, otherwise it assigns the next sequential ID. Consequences:

  • Second-table row numbers are NOT symbol IDs once any duplicate has occurred.
  • Example: ID 57 is mouseUp (from the fixed table), even though the second table's row 57 is beepOn; font lands at ID 751 only after earlier duplicates were skipped.
  • Aliases intern to one ID family by sharing descriptors instead: textStyle/fontStyle, textFont/font, textHeight/lineHeight, textAlign/alignment, textSize/fontSize pairs each register the same descriptor pointer under two IDs: the D4-era name and the D7+ name are literally the same property natively.

The API registry

A registry helper (FUN_680cba3c) stores descriptor pointers keyed by symbol ID:

c
slot = FUN_680dc6c0(table, symbolId, 1, 0);   // find-or-create row
*slot = descriptorPtr;

Two registrar functions make 169 registration calls total (44 + 125), covering the classic global property/function surface: ticks, memorySize, floatPrecision, primary handler scripts, date/time, coercion functions (integer, float, value, string, symbol), type predicates, sprite properties (locH, locV, ink, blend, foreColor, backColor, width, height, rect, stretch, puppet, cursor, moveableSprite, editableText...), text properties, timeout family, stage metrics (stageLeft/Right/Top/Bottom), mouse state (mouseH/V, clickOn, doubleClick, mouseLine/Item/Word/Char, mouseCast/Member), and system flags (colorDepth, exitLock, netPresent, safePlayer...).

All 169 rows were decoded: symbol ID, name, source table, and descriptor pointer. The registry is the bridge every the x expression crosses.

Dispatch route

  • Opcode 0x1f of the script VM is registered to a wrapper (FUN_680e5680) that: reads a VM value that must be a symbol (type tag 8), looks the ID up in the registry (lookup-only), and calls the shared executor FUN_680c8c3e with the registered descriptor. No descriptor found = fallback route (FUN_680c8b4c).
  • A parallel route (FUN_680e3e1a) handles set/mutation, calling the same executor with a setter flag.

Descriptor records

Descriptor pointers are data records, mostly 4-cell (16-byte) rows, sometimes chained 3-cell groups. First cell = flag word. Observed distribution across the 160 unique descriptors:

First wordCountFamily
0x4079Simple global/event/property (read via direct route).
0x41532Sprite-targeted property (... of sprite n).
0x41031Global writable property.
0x3154Member/cast/script text-style chains.
0x20, 0xd, 0x15, 0x215, 0x615, 0x26, 0x712Small special families (last, number, text, hilite, name, date formats...).

Examples (confirmed cells):

text
mouseUp   6814f9a8: [0x40,  0x39,  0x0,   0x0]     ; 0x39 = symbol ID of mouseUp
ticks     ........: [0x40,  0x12b, 0x0,   0x0]
locH      6814f3b0: [0x415, 0x2e,  0x60d, 0x0]     ; 0x2e = "sprite"
ink       6814f420: [0x415, 0x2e,  0x60a, 0x0]
blend     6814f450: [0x415, 0x2e,  0x61c, 0x0]
font      6814f850: [0x315, 0x58,  0x904, 0x315, 0x59, 0x904, 0x315, 0x7b, ...]
                                              ; 0x58 member, 0x59 cast, 0x7b script

The third cell of a target-property descriptor is a packed payload, split by the executor: low byte = property index delivered to the target object, next byte = route/type byte. So ink (payload 0x60a) becomes route 0x06, channel property index 0x0a; blend = 0x06/0x1c; locH = 0x06/0x0d.

Executor branch families

FUN_680c8c3e is the shared descriptor executor. Its confirmed flag bits:

BitEffect
0x1Read and match a target symbol (e.g. sprite, member). Mismatch = error 0x15.
0x4Consume/evaluate the target expression (sprite 1) before emitting.
0x8Marks continued target groups in chains (under study).
0x10Setter mode allowed; setter requires to or = next (error 0x0e on bad RHS syntax).
0x100Enables chunk-qualifier route: accepts char/word/item/line (0x20-0x23).
0x200Checks for an of castLib form.
0x40Simple direct branch (below).

Branch behavior:

  • 0x40 simple: if not followed by an of-target form, emits op 0x43, resolves the descriptor's second cell, emits op 0x66; rejects setter mode with error 0x14. With an of target, it enters the shared target path emitting 0x61 (get) / 0x62 (set).
  • 0x415 sprite property: match sprite, evaluate the sprite expression, split payload (low byte as immediate via the constant emitter, next byte as operand to the operation emitter), emit 0x5c (get) / 0x5d (set).
  • 0x410 global writable: same payload split and 0x5c/0x5d ops, but no target symbol and no target expression.
  • 0x315 text chains: iterate 3-cell groups (flags, target symbol, payload) covering member/cast/script targets with optional chunk qualifiers; e.g. the font of word 2 of member 1 walks this chain.

Helper semantics (confirmed): a VM value reader with push-back (probe-then-restore) support; a symbol reader accepting only of/in (0x3e/0x5f); a setter-syntax reader accepting only to/=; an operation emitter with byte/word/dword operand-width promotion; an immediate-constant emitter with dedicated zero/byte/word/dword encodings; and an output writer maintaining depth counters.

Native error codes

CodeCondition
0x0eSetter right-hand-side syntax invalid.
0x12Expected of/in form missing.
0x13Top-level value was not a symbol.
0x14Setter attempted on a read-only descriptor branch.
0x15Target symbol match failed in a descriptor chain.

What this means for emulators

  1. Implement the descriptor model, not string tables. The native design is: intern symbol, look up descriptor, interpret flags, match targets, split payloads, emit get/set ops. Emulators that hardcode per-name behavior drift on aliases (textFont vs font) and on chunk-qualified forms.
  2. Aliases are free when descriptors are shared, exactly as the native tables do.
  3. Payload property indexes are the channel contract: sprite ink is channel property 0x0a, blend is 0x1c; the channel object's getter/setter/dirty routes consume those indexes (rendering bridge).
  4. Still open: full semantics of payload high bytes in 0x315 chains (0x903, 0x904, 0x907), the meaning of emitted ops 0x43/0x61/0x62/0x66 beyond their roles, and chain termination rules. Treat unproven cells as research targets, not implementation guidance.