Skip to content
@mplx/d64

D64 cheatsheet

The full API surface. Guides: index.md. Runnable programs: examples/.

tools/check-docs.sh fails the build on an undocumented export, and on a signature here that does not match the code.

A topics deck: one namespace, spliced from src/topics/. Names are written bare here; with import "@mplx/d64/" as d64; they read d64.formatDisk(...).

jennifer
use io;
import "@mplx/d64/" as d64;

def img as d64.Image init d64.formatDisk("sample disk", "01");
$img = d64.writeText($img, "readme", d64.FileType.Seq, "hello\n");
io.printf("%s", d64.directoryText($img));
d64.save($img, "sample.d64");

Types

Image

A disk image in memory. Value-semantic: every function that changes a disk returns a fresh one.

FieldNotes
data as bytesthe blocks, then the optional error table
tracks as int35, 40 or 42
errorInfo as booltrue when data carries one error byte per block
format as DiskFormatwhich BAM layout the disk uses

A track/sector pair, the link CBM DOS chains blocks with.

FieldNotes
track as int1 to 42
sector as intwithin the track

Entry

One directory slot.

FieldNotes
name as stringthe filename, pad bytes trimmed, shifted reading
kind as FileTypeDel, Seq, Prg, Usr or Rel
closed as boolfalse marks a splat file the drive never closed
locked as booltrue marks a file SCRATCH refuses, listed <
track as intfirst block
sector as intfirst block
blocks as intlength as the directory records it
recordLength as intREL files only
slot as intposition in the directory chain, 0-based

Program

A PRG split at its load address.

FieldNotes
address as intthe load address the first two bytes carry
data as byteseverything after it

FileInfo

A file's metadata with its chain. A DEL entry reports an empty chain.

FieldNotes
entry as Entrythe directory entry, verbatim
chain as list of Linkevery block, first to last
blocks as intblocks actually in the chain
size as intpayload bytes over the whole chain
lastUsed as intpayload bytes in the last block
loadAddress as intthe PRG load address, -1 when there is none
blocksMatch as boolwhether the entry's block count is right

FormatOptions

What to format. A zero field takes its default.

FieldNotes
tracks as int35, 40 or 42; 0 means the format's own default
format as DiskFormatwhich BAM layout to write
errorInfo as booltrue to append the per-block error table
dosType as stringtwo characters; "" means "2a"
fill as intthe byte every block is set to, 0 to 255
mode as FormatModehow much to rewrite; only reformatWith reads it

DiskFormat

Which BAM layout a disk uses. Zero value Cbm.

VariantNotes
Cbmthe stock layout; tracks 36-42 have no BAM entry
SpeedDosentries for tracks 36-40 at $c0-$d3
DolphinDosentries for tracks 36-40 at $ac-$bf
PrologicDosentries at $90-$a3; the header moves to $a4

FileType

What kind of file a directory entry describes. Zero value Del.

VariantNotes
Dela scratched or decorative slot, not a file
Seqa stream of bytes
Prga program, with a two-byte load address in front
Usras Seq, named for the program's own use
Relrecord-oriented, with a side-sector index

FormatMode

How much of the disk a format rewrites. Zero value Full.

VariantNotes
Fullevery block to the fill byte, then the BAM and directory
Quickthe BAM and first directory block only; other blocks keep their bytes

Charset

Which of the C64's two character sets a PETSCII byte is read in. Zero value Shifted.

VariantNotes
Shifted$41-$5a lower case, $c1-$da upper case
Unshifted$41-$5a upper case, $c1-$da box drawing and shades

Formatting

FunctionReturnsNotes
formatDisk(name, id)Imageblank 35-track CBM disk, 664 blocks free. NEW0:NAME,ID on a 1541
formatDiskWith(options, name, id)Imageexplicit geometry and BAM layout
reformat(img, name, id)Imagefull wipe to $00, keeping geometry, layout and error table. N0:NAME,ID
quickFormat(img, name)Imageerase the BAM and directory only; other blocks keep their bytes, the ID is kept. N0:NAME
reformatWith(img, options, name, id)Imageexplicit mode, fill and dosType; geometry and layout always come from the image
defaultFormatOptions()FormatOptions35 tracks, Cbm, no error table, DOS type "2a", fill $00, mode Full
formatName(format)string"cbm" / "speeddos" / "dolphindos" / "prologicdos"
formatFromName(name)DiskFormatinverse, case-insensitive; also "1541", "commodore", "speed", "dolphin", "prologic", hyphenated
formatTracks(format)int35 for Cbm, 40 for the speeder layouts
detectFormat(bam, tracks)DiskFormatguess the layout from sector 18/0; used by fromBytes
headerOffsetFor(format)int$90, or $a4 under PrologicDOS
bamEntryOffset(format, track)intoffset of a track's four BAM bytes, -1 when it has none
jennifer
def opts as d64.FormatOptions init d64.FormatOptions{
    tracks: 40, format: d64.DiskFormat.SpeedDos, errorInfo: false,
    dosType: "", fill: 0, mode: d64.FormatMode.Full
};
def img as d64.Image init d64.formatDiskWith($opts, "forty track", "40");
d64.blocksFree($img);   # 749

Image

FunctionReturnsNotes
fromBytes(raw)Imagegeometry derived from the length
toBytes(img)bytesthe image, ready to write
open(path)Imageread one from a file
save(img, path)write one to a file
withFormat(img, format)Imageoverrule detectFormat. No bytes change
blockCount(img)int683, 768 or 802
readSector(img, track, sector)bytesexactly 256 bytes. The BAM is not consulted
writeSector(img, track, sector, block)Imagetakes exactly 256. The BAM is not consulted or updated
readBlocks(img, first, count)bytesa run of consecutive blocks, crossing track boundaries; count * 256 bytes
writeBlocks(img, first, data)Imagethe same in reverse; data is a whole multiple of 256 bytes
blockNumber(track, sector)intthe flat block index, 0 at 1/0
blockLink(number)Linkthe inverse
patchSector(img, track, sector, offset, data)Imageoverwrite part of one block, leaving the rest of it alone
readAt(img, offset, length)bytesthe flat byte view, block boundaries ignored; the error table is outside it
writeAt(img, offset, data)Imagethe same in reverse
findBytes(img, pattern)list of intevery offset the pattern occurs at, ascending. blockLink($at // SECTOR_SIZE) gives the address
sectorError(img, track, sector)intrecorded read result; ERROR_OK when there is no error table
setSectorError(img, track, sector, code)Imageraises without an error table
clearErrorTable(img)Imagemark every block good; no-op without a table

Geometry

FunctionReturnsNotes
sectorsPerTrack(track)int21, 19, 18 or 17
totalSectors(tracks)int683/768/802 at 35/40/42; 0 answers 0
sectorOffset(track, sector)intthe byte offset of a block
imageSize(tracks, errorInfo)intone of the six D64 sizes
tracksForSize(size)int35/40/42, or 0 when not a D64 size
sizeHasErrorInfo(size)boolwhether the size includes the error table

BAM

FunctionReturnsNotes
blocksFree(img)intallocatable free blocks, track 18 excluded; 664 on a fresh 35-track disk
trackBlocksFree(img, track)int0 when the track has no BAM entry
hasBamEntry(img, track)boolwhether the track is covered
isFree(img, track, sector)boolwhether the block is available
isAllocated(img, track, sector)boolthe inverse
allocateSector(img, track, sector)Imageraises if already taken
freeSector(img, track, sector)Imageraises if already free
nextFreeSector(img, near, interleave)Linkstay on near's track at the interleave, then spiral out from track 18. Raises on a full disk
trackSearchOrder(img, preferred)list of intthat spiral, preferred first when usable

Disk header

FunctionReturnsNotes
diskName(img)stringthe shifted reading
diskNameIn(img, charset)stringthe name in a chosen character set
setDiskName(img, name)Image
diskId(img)string
setDiskId(img, id)Image
dosType(img)string"2a" on anything a 1541 wrote
dosVersion(img)intthe byte at $02 of the BAM; $41 normally

Directory

FunctionReturnsNotes
listFiles(img)list of Entrylive files, in directory order
findFiles(img, pattern)list of Entrymatching a CBM pattern
findFile(img, name)Entryexact name; raises on a miss
hasFile(img, name)boolexact name, case-insensitive
directoryText(img)stringthe LOAD"$",8 listing
directoryTextIn(img, charset)stringthe same listing in a chosen character set; Charset.Unshifted shows border art as borders
nameBytes(img, entry)bytesthe 16 raw name bytes of an entry's slot, the lossless route to the other reading
renameFile(img, oldName, newName)Image
lockFile(img, name)ImageSCRATCH then refuses it
unlockFile(img, name)Image
directorySectors(img)list of Linkthe directory chain; raises if it loops or leaves track 18
fileTypeName(kind)string"del" / "seq" / "prg" / "usr" / "rel"
fileTypeFromName(name)FileTypeinverse, case-insensitive
fileTypeFromByte(raw)FileTypethe low nibble of a type byte
fileTypeByte(kind, closed, locked)int$82 is a closed PRG

Files

FunctionReturnsNotes
readFile(img, name)bytesraw; nothing transcoded. Refuses a DEL entry, which is a directory slot rather than a file
readText(img, name)stringPETSCII to text
readProgram(img, name)Programsplit at the load address
readChain(img, track, sector)bytesa chain no entry points at
chainOf(img, track, sector)list of Linkthe blocks a file uses
fileInfo(img, name)FileInfothe entry, the chain, the real size and the PRG load address. A DEL entry reports an empty chain
listFileInfo(img)list of FileInfothe same for every file
writeFile(img, name, kind, data)Imageraises on a taken name, a REL or DEL kind, or a full disk or directory
writeText(img, name, kind, text)Imagetranscodes to PETSCII
writeProgram(img, name, address, data)Imageload address first, low byte first
updateFile(img, name, kind, data)Imagereplace, or write if absent
patchFile(img, name, offset, data)Imageoverwrite bytes in place; chain, entry and BAM untouched. Cannot change the length
deleteFile(img, name)Imagescratch; raises on a locked file. A DEL entry loses its slot and frees no blocks

PETSCII

FunctionReturnsNotes
toPetscii(text)bytescase-swapped, the petcat convention. Charset.Shifted
fromPetscii(data)stringinverse; a byte with no glyph becomes ?
toPetsciiIn(text, charset)bytesthe same in a chosen character set
fromPetsciiIn(data, charset)stringthe same in a chosen character set
encodeName(name)bytes16 PETSCII bytes, pad-filled. Raises on an empty name, one over 16 characters, or one holding the pad byte
decodeName(raw)stringtrailing padding dropped
encodeNameIn(name, charset)bytesthe same in a chosen character set
decodeNameIn(raw, charset)stringthe same in a chosen character set
matchName(pattern, name)boolthe drive's * and ?, case-insensitive

Constants

NameValueWhat it is
SECTOR_SIZE256bytes in one block
DATA_BYTES_PER_SECTOR254payload bytes per block
DIRECTORY_TRACK18the track CBM DOS keeps for itself
BAM_SECTOR0the BAM, on that track
FIRST_DIRECTORY_SECTOR1where the directory chain starts
TRACKS_STANDARD35a stock 1541 disk
TRACKS_EXTENDED40what the speeder DOSes formatted
TRACKS_MAX42the largest D64 in circulation
FILE_INTERLEAVE10sectors skipped between file blocks
DIRECTORY_INTERLEAVE3sectors skipped between directory blocks
ENTRIES_PER_SECTOR8directory slots in one block
ENTRY_SIZE32bytes in one slot
MAX_DIRECTORY_ENTRIES14418 blocks of 8
MAX_FILENAME16characters CBM DOS stores
PAD_BYTE$a0the shifted space a short name is padded with
SUBSTITUTE_BYTE$3fwhat an unmappable character becomes
DOS_VERSION_BYTE$41the byte at $02 of the BAM
DOS_TYPE"2a"the DOS type a 1541 stamps in
HEADER_OFFSET$90where the disk header sits
HEADER_OFFSET_PROLOGIC$a4where PrologicDOS moved it
ERROR_OK$01the error code of a good block
ERROR_KIND"d64"the kind every Error here carries
VERSIONgeneratedthe deck version, injected from the git tag