{
 "chunk": "drive_driveFastLoader_0300",
 "unit": "drive/driveFastLoader",
 "unitLabels": {
  "drive/driveFastLoader": {
   "030F": "waitSeekJobAtBoot",
   "032A": "handleReadCommand",
   "033F": "resetDrive",
   "0342": "enableByteReadySo",
   "034F": "exchangeNextBit",
   "035A": "waitHostPullsClk",
   "036B": "waitHostReleasesClk",
   "0382": "waitHostTogglesData",
   "0397": "findHeaderAndDataSync",
   "03C5": "tryNextHeader",
   "03CA": "compareHeaderByte",
   "03DB": "headerMismatch",
   "03E0": "reportErrorAndLoop",
   "03EA": "waitSyncOrTimeout",
   "0400": "waitSeekJobBeforeRead",
   "040F": "readGcrIntoBuffer",
   "041D": "readGcrOverflow",
   "0452": "sendNextByte",
   "0469": "waitHostAsksForByte",
   "04A4": "waitSeekJobBeforeWrite",
   "04AE": "receiveDataByte",
   "04CA": "writeProtectOrIdError",
   "04D7": "skipHeaderGap",
   "04F4": "writeSyncBytes",
   "04FC": "writeGcrOverflow",
   "0508": "writeGcrFromBuffer",
   "0540": "cryptNextByte",
   "0541": "cryptKeyEor",
   "0550": "cryptExit",
   "0556": "uploadStoreByte",
   "057D": "lastCommandLda",
   "057F": "commandExitBeq",
   "0581": "commandExitRts"
  }
 },
 "notes": {
  "0300": {
   "unit": "drive/driveFastLoader",
   "routine": [
    "fastloaderMain - Entry point of the 1541-side fast loader.  The bootstrap sector (T1 s17) has already read T1 s18-20 into $0300-$05FF, i.e. over DOS buffers 0, 1 and 2, and jumps here.  It idles the serial bus, points buffer 0's header table entry at track 18 sector 0 and issues a SEEK job so the DOS job controller parks the head on the directory track, then falls into the command loop.",
    "In:  nothing.  DOS has just been re-initialised by the bootstrap (it calls ROM_INIT_DRIVE at $063B), so zp_12/zp_13 hold the disk id and the controller interrupt is running.",
    "Out: serial bus idle, zp_06/zp_07 = 18/0, buffer 0's SEEK job run to completion (result ignored), zp_30-zp_33 left by the controller pointing at buffer 0 and at its header table entry; falls through into commandDispatchLoop.",
    "Called from: drive/driveBootstrap $063E (JMP $0300) - the only way into this file.  XREF.txt has no entries for drive code: its $0300-$05FF addresses collide with C64 RAM, so the XREF rows for these addresses belong to unrelated C64 units."
   ],
   "line": "$00 = release both serial outputs (CLK OUT and DATA OUT idle, ATNA off)"
  },
  "0313": {
   "unit": "drive/driveFastLoader",
   "routine": [
    "commandDispatchLoop - The drive's command loop.  It rebuilds the stack pointer the way the DOS idle loop does (every error path abandons the stack by jumping straight back here), receives one command byte and dispatches it: $C0 = cold start the drive, $60 = write a sector, anything else = read a sector (the C64 always sends $80).  Read and write are each followed by a track byte and a sector byte, and every operation ends by sending a status byte, 0 meaning no error.",
    "In:  the command byte returned by receiveCommand.",
    "Out: never returns.  Each pass leaves a sector in buffer 3 ($0600) and a status byte on the wire.",
    "Called from: falls in from fastloaderMain; jumped back to by sendStatusAndLoop ($0394) and handleWriteCommand ($049D)."
   ],
   "line": "hold off the controller interrupt while the stack pointer is rebuilt"
  },
  "0342": {
   "unit": "drive/driveFastLoader",
   "routine": [
    "enableByteReadySo - Sets VIA2's PCR bits 1-3 to %111, i.e. CA2 = manual output high.  CA2 is the 1541's SOE line: while it is high the disk controller's BYTE READY pulse reaches the 6502's SO pin, which is what makes every 'bvc *' wait in this file (and in the DOS ROM helpers it calls) work.  It does not select read mode - that is CB2, PCR bits 5-7, which writeSectorToDisk drives directly - so the survey name setHeadReadMode was misleading.",
    "In:  VIA2_PCR.",
    "Out: PCR bits 1-3 set; from here on every assembled disk byte sets the V flag.",
    "Called from: prepareHeadAndVerifyId $0525, which both the read and the write path call."
   ],
   "line": "peripheral control register: CA1/CA2 in the low nibble, CB1/CB2 in the high nibble"
  },
  "034B": {
   "unit": "drive/driveFastLoader",
   "routine": [
    "exchangeByteWithHost - The slow, fully handshaked, full-duplex byte transfer with the C64.  Eight times over, the drive puts one outgoing bit (LSB first) on DATA, waits for the host to pull CLK, toggles DATA to say 'seen', waits for the host to release CLK, samples the host's bit off DATA, pulls CLK as its own acknowledgement and waits for the host to toggle DATA.  So both directions move one bit per handshake and neither side has to be cycle-accurate.  The host side is sendByteToDrive ($089C in game/mainProgram0800, bootExchangeByte $C03B in boot/fastLoaderC000).",
    "Note the inversion at the drive: writing a 1 to a VIA1 serial output bit PULLS that line low, and an input bit READS 1 while the line is low.  Bit 0 = DATA IN, bit 1 = DATA OUT, bit 2 = CLK IN, bit 3 = CLK OUT.",
    "In:  A = byte to send (kept in zp_90).",
    "Out: A = the byte received from the host (zp_8F); zp_14 holds the port snapshot the toggle test compares against; X = 0.",
    "Called from: commandDispatchLoop $032A/$032F (track and sector), sendStatusAndLoop $0391 (status byte), handleWriteCommand $0490/$0495, writeSectorToDisk $04AE (all 256 data bytes) and $0522 (final status), receiveCommand $056B, uploadBytesToDrive $0553."
   ],
   "line": "the byte to send goes into the output shift register"
  },
  "0391": {
   "unit": "drive/driveFastLoader",
   "routine": [
    "sendStatusAndLoop - Sends the byte in A to the host as the operation's status and restarts the command loop.  0 means success; the error paths arrive here with a DOS error code: $02 header not found (READ ERROR 20), $03 no sync (21), $05 data block checksum (23), $08 write protected (26) - the last one doubling as the wrong-disk-id code.  The C64 treats any nonzero status as a failure, turns the border red and retries the same sector forever, so any of these hangs the load.",
    "In:  A = status byte.",
    "Out: does not return; jumps to commandDispatchLoop, which rebuilds the stack pointer over whatever the abandoned call chain left behind.",
    "Called from: commandDispatchLoop $033C (status 0 after a read), findSectorHeader $03E0 (also the landing point for waitForSyncByte's timeout), readSectorFromDisk $0435, writeSectorToDisk $04CC, and the unreferenced helper at $05E2."
   ],
   "line": "the host is exchanging a dummy byte to collect this ($0949/$C0E8)"
  },
  "0397": {
   "unit": "drive/driveFastLoader",
   "routine": [
    "findHeaderAndDataSync - Positions the head at the data field of the sector named in the header table: first waits for that sector's header to come round (findSectorHeader), then for the sync mark that introduces the data block (waitForSyncByte).  It comes back with Y = 0 and V clear so the caller can fall straight into a byte-ready read loop.  Survey name was seekSectorAndSyncData; nothing seeks here - the SEEK job in readSectorFromDisk did that.",
    "In:  zp_12/zp_13 disk id, zp_3D drive number, (zp_32) = the header table entry holding track and sector.",
    "Out: the next BYTE READY is the first GCR byte of the data field; Y = 0, V clear.  Header or sync failures never return - they go to sendStatusAndLoop.",
    "Called from: readSectorFromDisk $040C."
   ],
   "line": "wait for the header of the wanted track/sector to pass under the head"
  },
  "039D": {
   "unit": "drive/driveFastLoader",
   "routine": [
    "findSectorHeader - Builds the header image DOS would have written for the wanted sector (disk id, track, sector and their XOR checksum), has the ROM turn it into GCR at zp_24, then compares the first eight GCR bytes of every header that passes under the head against it.  It returns the moment one matches, with the head sitting just behind that header; after 90 headers (roughly four revolutions) it gives up with error $02.",
    "In:  zp_3D drive number, zp_12/zp_13 disk id, (zp_32) pointing at the wanted track/sector pair.",
    "Out: RTS with the head just past the matching header.  Clobbers zp_16-zp_1A (header image) and zp_24-zp_2D (its GCR form).  On failure it jumps to sendStatusAndLoop with $02 and never returns.",
    "Called from: findHeaderAndDataSync $0397 on the read path and writeSectorToDisk $04D2 on the write path."
   ],
   "line": "DOS drive number for the current job - always 0 in a single 1541"
  },
  "03E3": {
   "unit": "drive/driveFastLoader",
   "routine": [
    "waitForSyncByte - Waits for the next SYNC mark under the head, guarded by VIA1 timer 1 as a timeout of roughly 20 ms (about a tenth of a revolution, several sectors' worth).  On sync it does one dummy read of the data port, which clears the pending BYTE READY, so the caller's next 'bvc' wait yields the first real byte after the sync mark.  This is the DOS ROM routine at $F556 copied into RAM.",
    "In:  VIA1 timer 1, VIA2 disk control port (bit 7 = SYNC, low while a sync mark is passing).",
    "Out: A = the byte read while still inside the sync (both callers discard it), V clear, Y = 0.  On timeout it does not return: A is already $03 (DOS 'no sync', READ ERROR 21) and it branches into the shared error exit at $03E0.",
    "Called from: findHeaderAndDataSync $039A (the data block's sync) and findSectorHeader $03C5 (before every header comparison)."
   ],
   "line": "timeout counter high byte"
  },
  "03FB": {
   "unit": "drive/driveFastLoader",
   "routine": [
    "readSectorFromDisk - Reads the sector named by zp_06/zp_07 into buffer 3 ($0600) and decrypts it.  It issues a SEEK job to get the head onto the track, takes the disk hardware over from the DOS controller (SEI), waits for the sector's data field, shovels 326 raw GCR bytes into $0600 and $01BA-$01FF, lets the ROM decode and checksum them, and tail-jumps into cryptSectorBuffer.",
    "In:  zp_06 = track, zp_07 = sector, both just received from the host.",
    "Out: 256 plaintext bytes at $0600.  Interrupts stay disabled until the command loop does CLI.  Returns through cryptSectorBuffer's shared exit at $0581.  A checksum failure reports $05 and never returns.",
    "Called from: commandDispatchLoop $0334."
   ],
   "line": "leftover: nothing below depends on the carry"
  },
  "044B": {
   "unit": "drive/driveFastLoader",
   "routine": [
    "sendSectorToHost - Ships the 256 plaintext bytes of buffer 3 to the C64 over the fast path: no per-bit handshake, just four port writes per byte, two data bits at a time on CLK OUT and DATA OUT, encoded through nibbleSendTable.  The only synchronisation is once per byte - the host releases the DATA line to ask for the next one and then samples the four writes at fixed cycle offsets.  That is why the C64 side stops CIA1 timer A, blanks its sprites and waits for a raster line that is not about to be a bad line before each byte (receiveSectorData $0950 / bootReceiveSectorData $C0EF), and why the padding NOPs here matter.",
    "In:  256 decrypted bytes at $0600, nibbleSendTable at $043B.",
    "Out: 256 bytes clocked to the host; the bus is parked with CLK pulled low, ready for the status exchange.  A, X and Y clobbered.",
    "Called from: commandDispatchLoop $0337."
   ],
   "line": "$08 = CLK OUT"
  },
  "0490": {
   "unit": "drive/driveFastLoader",
   "routine": [
    "handleWriteCommand - Handler for command $60: take the track and sector bytes from the host, write the sector, go back to the command loop.  The C64 only uses this to save game films and the setup - $7E85 and $8194 in the map generator overlay call writeSectors ($0843), which is the only code that sends $60.",
    "In:  two bytes from the host.",
    "Out: zp_06/zp_07 set and the sector written; the status byte is sent by writeSectorToDisk itself.",
    "Called from: commandDispatchLoop $0327."
   ],
   "line": "receive the track number"
  },
  "04A0": {
   "unit": "drive/driveFastLoader",
   "routine": [
    "writeSectorToDisk - Receives 256 bytes from the host the slow handshaked way, encrypts them with the same rolling XOR that a read decrypts with, checksums them, refuses a write-protected disk ($08), lets the ROM turn the block into GCR, waits for the sector's header to come round, switches the head electronics to write, lays down five sync bytes and the 325 GCR bytes of the data field, switches back to read and reports success.",
    "In:  zp_06/zp_07 = track/sector, 256 bytes arriving on the serial link.",
    "Out: the sector on disk, encrypted; zp_3A = the data block checksum.  The status byte goes out from the tail call at $0522, whose RTS returns to handleWriteCommand.",
    "Called from: handleWriteCommand $049A."
   ],
   "line": "$B0 = DOS job code SEEK"
  },
  "0525": {
   "unit": "drive/driveFastLoader",
   "routine": [
    "prepareHeadAndVerifyId - Enables BYTE READY -> SO and then checks the disk: zp_12 EOR zp_13 has to equal $15, and $15 is 'O' EOR 'Z' - the id of the SPORT OF WAR master.  Any other id (a disk formatted with id 'EA' XORs to $04) drops into the write-protect error exit, so the C64 loader spins forever with a red border.  Cheap protection: a sector copy carries the id along, a file-by-file copy onto another blank does not.  The survey noted the $15 as an unexplained DOS constant.",
    "In:  zp_12/zp_13 = the disk id read by the last INITIALIZE (the bootstrap called ROM_INIT_DRIVE at $063B).",
    "Out: VIA2 PCR CA2 set.  On a mismatch it jumps to $04CA (status $08) and never returns.",
    "Called from: readSectorFromDisk $0405 and writeSectorToDisk $04A9."
   ],
   "line": "let BYTE READY drive the CPU's SO pin"
  },
  "0531": {
   "unit": "drive/driveFastLoader",
   "routine": [
    "cryptSectorBuffer - The disk cipher, and the reason a plain sector dump of this disk is unreadable.  Track 18 is left in plain text (BAM, directory, the unit start-position templates, the tile rules and the save slots); every other sector is XORed with a rolling key seeded from its own track and sector, so the same plaintext encrypts differently in each sector: seed = ((track OR $C0) * 2) + sector + carry, key[0] = ROL(seed), key[y] = ROL(y EOR key[y-1]), the carry chaining from one ROL into the next.  The current key byte lives in the operand of the EOR at $0542 and is rewritten every round.  Being a pure XOR it is symmetric - the same code decrypts a read and encrypts a write.",
    "In:  zp_06 = track, zp_07 = sector, 256 bytes at $0600.",
    "Out: $0600-$06FF transformed in place; leaves through the shared tail at $057D, so A comes back holding the last command byte.",
    "Called from: readSectorFromDisk $0438 (JMP, so this routine's exit is what returns to commandDispatchLoop) and writeSectorToDisk $04B7 (JSR).  extracted/decryptDisk.py reimplements it; see overview section 4."
   ],
   "line": "track the sector came from"
  },
  "0553": {
   "unit": "drive/driveFastLoader",
   "routine": [
    "uploadBytesToDrive - RAM injection.  receiveCommand comes here when the host's command byte is >= $F0; that byte is really a negative count, so 256 minus it more bytes are received and poked into drive RAM starting at $0580, with both the destination low byte (the operand of the STA at $0556) and the counter (the operand of the LDA at $057D) self-modified as it goes.  When the counter wraps to zero the code falls into receiveCommand for the real command - and by then the injected bytes have landed on the BEQ operand at $0580 and the RTS at $0581, so receiveCommand's own tail has become the injected code, which runs and returns whatever it likes in A.  $0582-$05DD is prefilled with RTS so a short upload still ends cleanly, and the destination pointer is never reset, so successive uploads append.",
    "In:  the count already patched into $057E by receiveCommand, then that many bytes on the serial link.",
    "Out: bytes stored from $0580 upward; $0557 and $057E left patched; falls into receiveCommand.",
    "Called from: receiveCommand $0573.  Nothing in the boot or overlay-loading path ever sends a command byte >= $F0, but the modem driver does: a received packet whose control byte is >= $F0 is forwarded byte for byte to the drive by dispatchReceivedPacket ($EA0D-$EA29 in game/modemDriverE000), counter and all."
   ],
   "line": "receive the next byte to inject"
  },
  "0561": {
   "unit": "drive/driveFastLoader",
   "routine": [
    "receiveCommand - Waits for one command byte from the host.  It turns the drive LED off while idle - that is the flicker you see between sectors - exchanges a byte (it sends $01, which the host ignores) and stores what arrived in the operand of the LDA at $057D, which doubles as the upload counter.  A value >= $F0 is not a command but the length preamble of a RAM upload, so control drops into uploadBytesToDrive; otherwise the LED goes back on and the byte is handed back through the shared exit at $057D-$0581.",
    "In:  nothing.",
    "Out: A = the command byte; LED on; $057E patched with the same value.",
    "Called from: commandDispatchLoop $031C, and by falling out of uploadBytesToDrive."
   ],
   "line": "disk control port: stepper, motor, LED, write protect sense, SYNC"
  },
  "05DE": {
   "unit": "drive/driveFastLoader",
   "routine": [
    "finishSendByte - Two-instruction tail of sendSectorToHost, oddly parked out here in the third loader sector: it writes A ($08 = CLK pulled low) to the serial port and returns, leaving the bus in the state exchangeByteWithHost expects for the status exchange that follows.  There was room to do it inline at $048F, so this looks like a leftover of how the loader was assembled rather than something deliberate.",
    "In:  A = the port value to park ($08, loaded at $048A).",
    "Out: VIA1 serial port written; RTS to commandDispatchLoop.",
    "Called from: sendSectorToHost $048D (JMP)."
   ],
   "line": "park the bus with CLK pulled low"
  },
  "05E2": {
   "unit": "drive/driveFastLoader",
   "routine": [
    "checksumBufferAndReport - 6502 code that the disassembler emits as data, because it sits behind an RTS and nothing in the drive reaches it.  $05E2 is JSR $05E8 / JMP sendStatusAndLoop; $05E8 is LDA #$00 / TAY / CLC and then the loop ROL A / ADC $0600,Y / INY / BNE, ending in RTS - a rotate-and-add checksum over the whole of buffer 3, handed to the host as the operation's status byte.",
    "The only thing that can call it is code injected through uploadBytesToDrive (a JMP $05E2 is three bytes, well inside one upload), which fits a mastering or verification tool; failing that it is dead code.",
    "In:  256 bytes at $0600.",
    "Out: A = the rolling checksum, sent to the host, then back to the command loop.",
    "Called from: nothing in this file."
   ],
   "line": "20 E8 05 = JSR $05E8, 4C 91 03 = JMP sendStatusAndLoop, then the helper starts with A9 00 = LDA #$00"
  },
  "030F": {
   "unit": "drive/driveFastLoader",
   "block": [
    "Wait for the seek.  The DOS job controller runs off the VIA2 interrupt; when it is done it clears bit 7 of the job byte and leaves a result code (1 = ok) in its place.  The result is never examined - all this needs is for the head to be somewhere known before the first command arrives."
   ],
   "line": "poll buffer 0's job byte"
  },
  "032A": {
   "unit": "drive/driveFastLoader",
   "block": [
    "Read command.  The two bytes that follow name the sector; read it, decrypt it, ship it out over the fast path and acknowledge with status 0."
   ],
   "line": "receive the track number (the byte handed back to the host is ignored)"
  },
  "034F": {
   "unit": "drive/driveFastLoader",
   "block": [
    "One bit per pass.  Phase 1 puts our bit on DATA and waits for the host's CLK; phase 2 toggles DATA as an acknowledgement and waits for CLK to go away; phase 3 samples the host's bit; phase 4 pulls CLK as our acknowledgement and waits for the host to toggle DATA back."
   ],
   "line": "shift the next outgoing bit (LSB first) into the carry"
  },
  "03C5": {
   "unit": "drive/driveFastLoader",
   "block": [
    "Header search.  Every sync mark on the track introduces either a header or a data block; read eight GCR bytes after each one and compare.  A mismatch costs one of the 90 tries and the search waits for the next sync."
   ],
   "line": "wait for the next sync mark"
  },
  "03EA": {
   "unit": "drive/driveFastLoader",
   "block": [
    "Poll two things at once: the timeout timer and the SYNC line."
   ],
   "line": "N = bit 7 of the timer high byte"
  },
  "040F": {
   "unit": "drive/driveFastLoader",
   "block": [
    "Pull in the data field.  The first 256 raw GCR bytes go into buffer 3 and the remaining 70 into $01BA-$01FF, which is where the ROM's GCR decoder expects the overflow - the layout is copied verbatim from the DOS read loop at $F556.  $0100-$0145 is left alone because that is where the stack lives."
   ],
   "line": "wait for the next assembled GCR byte"
  },
  "0429": {
   "unit": "drive/driveFastLoader",
   "block": [
    "Decode and verify: 326 GCR bytes become the 256 data bytes plus the block's own checksum byte, and that checksum has to match a recomputed one."
   ],
   "line": "$F8E0 converts the GCR back to binary in place and leaves the block's checksum byte in zp_3A"
  },
  "043B": {
   "unit": "drive/driveFastLoader",
   "block": [
    "nibbleSendTable - one byte per nibble value, used by sendSectorToHost; the index is the nibble to send, the entry is what gets written to the serial port.",
    "Only VIA1 bits 3 (CLK OUT) and 1 (DATA OUT) reach the C64, and writing a 1 pulls that line low, so the host reads back the complement.  Each entry is written twice: once as it stands (bits 3 and 1 land on CLK and DATA) and once shifted left by one (bits 2 and 0 land there), which is how four data bits travel in two port writes.  Working the encoding backwards from the host's receive loop, entry = the nibble complemented with bits 0 and 3 exchanged: $0 -> $0F, $1 -> $07, $2 -> $0D, $8 -> $0E, $F -> $00.",
    "The C64 loop at $0950 / $C0EF samples the (CLK,DATA) pair four times per byte and drops the pairs into bits 0-1, 2-3, 4-5 and 6-7, so the low nibble goes first."
   ],
   "line": "entries for nibbles $0-$F; entry = nibble complemented with bits 0 and 3 swapped"
  },
  "0452": {
   "unit": "drive/driveFastLoader",
   "block": [
    "One pass per buffer byte: encode the low nibble, wait for the host to ask for the byte, then fire four port writes - low nibble bits 0-1 and 2-3, high nibble bits 4-5 and 6-7."
   ],
   "line": "the byte to send"
  },
  "04AE": {
   "unit": "drive/driveFastLoader",
   "block": [
    "Take the 256 data bytes from the host.  This uses the slow handshaked path in both directions - saving a game film happens once in a while, so a fast host-to-drive protocol was never written."
   ],
   "line": "receive one data byte (the byte handed back to the host is ignored)"
  },
  "04C3": {
   "unit": "drive/driveFastLoader",
   "block": [
    "Refuse to write to a protected disk."
   ],
   "line": "disk control port"
  },
  "04CF": {
   "unit": "drive/driveFastLoader",
   "block": [
    "Encode the block and get into position: the write has to start in the gap right behind this sector's header."
   ],
   "line": "$F78F encodes the block id, the 256 data bytes and the checksum into 325 GCR bytes: the first 69 at $01BB-$01FF, the other 256 in buffer 3"
  },
  "04DD": {
   "unit": "drive/driveFastLoader",
   "block": [
    "Switch the head electronics to write and lay down the sync mark that introduces the data field."
   ],
   "line": "all eight lines to output"
  },
  "04FA": {
   "unit": "drive/driveFastLoader",
   "block": [
    "Write the data field: 69 GCR bytes from $01BB-$01FF first, then the 256 in buffer 3.  That is the order the ROM's binary-to-GCR encoder produced them in, and it is the mirror image of the read layout above - each direction matches the ROM helper it feeds."
   ],
   "line": "the encoded block starts with the 69 bytes at $01BB-$01FF"
  },
  "0513": {
   "unit": "drive/driveFastLoader",
   "block": [
    "Back to read mode and report success."
   ],
   "line": "wait for the last byte to be shifted out before killing write mode - cutting it short would corrupt the tail of the block"
  },
  "0540": {
   "unit": "drive/driveFastLoader",
   "block": [
    "The key stream: every round rotates (buffer index EOR previous key) left through the carry the previous round left behind, so the key depends on the whole history, not just the index."
   ],
   "line": "every later round starts from the buffer index"
  },
  "0582": {
   "unit": "drive/driveFastLoader",
   "block": [
    "driveUploadScratch - 92 bytes of $60 (RTS), $0582-$05DD.",
    "This is the landing area for uploadBytesToDrive: the first injected byte overwrites the BEQ operand at $0580 and the second the RTS at $0581, so injected code executes as the tail of receiveCommand and any byte it did not overwrite is a harmless return.  One upload can carry at most 16 bytes ($F0 = -16), but the destination pointer at $0557 is never reset, so uploads chain - which is why the RTS field is 92 bytes long."
   ],
   "line": "$60 = RTS, so an under-filled upload still returns cleanly"
  },
  "05F4": {
   "unit": "drive/driveFastLoader",
   "block": [
    "hiddenSignature - the last twelve bytes of the third loader sector: 44 61 4E 73 A9 55 F0 E2 53 61 52 61, which reads as 'DaNs' and 'SaRa' around four binary bytes.",
    "Nothing references the address and it is not reachable as code, so this is filler with a personal signature in it - the same kind of thing as the 1988 Electronic Arts message that fills the tail of the bootstrap sector.  Read as instructions the middle bytes would be LDA #$55 / BEQ, which is almost certainly coincidence."
   ],
   "line": "'DaNs' followed by $A9 $55; the last four bytes at $05FC are 'SaRa'"
  },
  "0302": {
   "unit": "drive/driveFastLoader",
   "line": "let go of the serial bus so the C64 sees CLK and DATA high"
  },
  "0305": {
   "unit": "drive/driveFastLoader",
   "line": "sector byte of buffer 0's header table entry ($06/$07) = 0"
  },
  "0307": {
   "unit": "drive/driveFastLoader",
   "line": "$12 = 18, the directory track"
  },
  "0309": {
   "unit": "drive/driveFastLoader",
   "line": "track byte of the same entry: the SEEK below steps the head to track 18"
  },
  "030B": {
   "unit": "drive/driveFastLoader",
   "line": "$B0 = DOS job code SEEK (step to the track in the header table and read a header there)"
  },
  "030D": {
   "unit": "drive/driveFastLoader",
   "line": "zp_00 is buffer 0's job byte - writing it hands the job to the controller"
  },
  "0311": {
   "unit": "drive/driveFastLoader",
   "line": "bit 7 stays set while the job is pending: spin until the controller replaces it"
  },
  "0314": {
   "unit": "drive/driveFastLoader",
   "line": "clear DOS work byte zp_50 (a controller state byte; exact ROM meaning uncertain)"
  },
  "0316": {
   "unit": "drive/driveFastLoader",
   "line": "error exits abandon the stack, so start every command from a known state"
  },
  "0318": {
   "unit": "drive/driveFastLoader",
   "line": "$45 is the stack pointer DOS itself uses: the stack stays inside $0100-$0145 and leaves $0146-$01FF free for the GCR overflow area used by the read and write paths"
  },
  "031A": {
   "unit": "drive/driveFastLoader",
   "line": "throw away anything a previous error path left on the stack"
  },
  "031B": {
   "unit": "drive/driveFastLoader",
   "line": "let the controller interrupt run again - the SEEK jobs below need it"
  },
  "031C": {
   "unit": "drive/driveFastLoader",
   "line": "LED off, then bit-bang one command byte in from the C64"
  },
  "031F": {
   "unit": "drive/driveFastLoader",
   "line": "$C0 = reset the drive?"
  },
  "0321": {
   "unit": "drive/driveFastLoader",
   "line": "yes: cold start and forget this loader"
  },
  "0323": {
   "unit": "drive/driveFastLoader",
   "line": "$60 = write a sector?"
  },
  "0325": {
   "unit": "drive/driveFastLoader",
   "line": "no: anything else means read a sector (the host only ever sends $80)"
  },
  "0327": {
   "unit": "drive/driveFastLoader",
   "line": "the write handler receives its own track/sector bytes"
  },
  "032D": {
   "unit": "drive/driveFastLoader",
   "line": "track byte of buffer 0's header table entry"
  },
  "032F": {
   "unit": "drive/driveFastLoader",
   "line": "receive the sector number"
  },
  "0332": {
   "unit": "drive/driveFastLoader",
   "line": "sector byte of the same entry - findSectorHeader reads both through (zp_32)"
  },
  "0334": {
   "unit": "drive/driveFastLoader",
   "line": "read it into buffer 3 ($0600) and decrypt it in place"
  },
  "0337": {
   "unit": "drive/driveFastLoader",
   "line": "clock the 256 bytes over with the fast nibble protocol"
  },
  "033A": {
   "unit": "drive/driveFastLoader",
   "line": "status 0 = no error; the C64 turns the border red and retries forever for anything else"
  },
  "033C": {
   "unit": "drive/driveFastLoader",
   "line": "send it and go back for the next command"
  },
  "033F": {
   "unit": "drive/driveFastLoader",
   "line": "$C0: jump through the drive's RESET vector at $FFFC - a full DOS cold start, which unloads this loader"
  },
  "0345": {
   "unit": "drive/driveFastLoader",
   "line": "bits 1-3 = %111: CA2 manual output high = SOE, so BYTE READY reaches the CPU's SO pin"
  },
  "0347": {
   "unit": "drive/driveFastLoader",
   "line": "the CB2 bits (read/write head select) are left exactly as they were"
  },
  "034A": {
   "unit": "drive/driveFastLoader",
   "line": "back to prepareHeadAndVerifyId, which then checks the disk id"
  },
  "034D": {
   "unit": "drive/driveFastLoader",
   "line": "eight bits per byte"
  },
  "0351": {
   "unit": "drive/driveFastLoader",
   "line": "a 1 bit: leave DATA OUT clear so the line stays high"
  },
  "0353": {
   "unit": "drive/driveFastLoader",
   "line": "branch if the outgoing bit is 1"
  },
  "0355": {
   "unit": "drive/driveFastLoader",
   "line": "a 0 bit: set DATA OUT (bit 1), which pulls the DATA line low"
  },
  "0357": {
   "unit": "drive/driveFastLoader",
   "line": "drive the bit onto DATA and keep CLK OUT released; the host samples it at $08AE/$C04D"
  },
  "035A": {
   "unit": "drive/driveFastLoader",
   "line": "poll the serial port"
  },
  "035D": {
   "unit": "drive/driveFastLoader",
   "line": "bit 2 = CLK IN, which reads 1 while the host holds the CLK line low"
  },
  "035F": {
   "unit": "drive/driveFastLoader",
   "line": "wait until the host pulls CLK - that means it has taken our bit"
  },
  "0361": {
   "unit": "drive/driveFastLoader",
   "line": "read the port back (the two output bits read back as written)"
  },
  "0364": {
   "unit": "drive/driveFastLoader",
   "line": "flip our own DATA OUT bit"
  },
  "0366": {
   "unit": "drive/driveFastLoader",
   "line": "toggling DATA is the acknowledgement the host is waiting for in its 'cmp CIA2_PRA / beq' loop at $08BD/$C05C"
  },
  "0369": {
   "unit": "drive/driveFastLoader",
   "line": "$04 = the CLK IN mask, and also the value the BIT below tests against"
  },
  "036B": {
   "unit": "drive/driveFastLoader",
   "line": "Z = 0 while CLK IN is still set"
  },
  "036E": {
   "unit": "drive/driveFastLoader",
   "line": "wait for the host to release CLK; by then its own data bit is sitting on the DATA line"
  },
  "0370": {
   "unit": "drive/driveFastLoader",
   "line": "release both outputs"
  },
  "0372": {
   "unit": "drive/driveFastLoader",
   "line": "let go of DATA OUT so the line shows only what the C64 drives"
  },
  "0375": {
   "unit": "drive/driveFastLoader",
   "line": "sample the port with the host's bit on DATA IN (bit 0)"
  },
  "0378": {
   "unit": "drive/driveFastLoader",
   "line": "remember the sampled port state for the toggle test at $0382 (zp_14/zp_15 are drive 1's disk id, which a single 1541 never uses)"
  },
  "037A": {
   "unit": "drive/driveFastLoader",
   "line": "host's data bit (bit 0) into the carry"
  },
  "037B": {
   "unit": "drive/driveFastLoader",
   "line": "shift it into the receive register from the top; after eight bits the first bit sits in bit 0, i.e. LSB first"
  },
  "037D": {
   "unit": "drive/driveFastLoader",
   "line": "$08 = CLK OUT"
  },
  "037F": {
   "unit": "drive/driveFastLoader",
   "line": "pull CLK low as our acknowledgement; the host waits for exactly this at $08D6/$C075"
  },
  "0382": {
   "unit": "drive/driveFastLoader",
   "line": "the port state at sample time"
  },
  "0384": {
   "unit": "drive/driveFastLoader",
   "line": "against the port right now"
  },
  "0387": {
   "unit": "drive/driveFastLoader",
   "line": "only DATA IN matters"
  },
  "0389": {
   "unit": "drive/driveFastLoader",
   "line": "wait until the host toggles DATA (its 'eor #$20' at $08DB/$C07A) - it has seen the acknowledgement"
  },
  "038B": {
   "unit": "drive/driveFastLoader",
   "line": "one bit done"
  },
  "038C": {
   "unit": "drive/driveFastLoader",
   "line": "round again for all eight"
  },
  "038E": {
   "unit": "drive/driveFastLoader",
   "line": "return the byte received from the host"
  },
  "0390": {
   "unit": "drive/driveFastLoader",
   "line": "callers that only wanted to send simply ignore it"
  },
  "0394": {
   "unit": "drive/driveFastLoader",
   "line": "abandon the stack and wait for the next command"
  },
  "039A": {
   "unit": "drive/driveFastLoader",
   "line": "then for the data block's sync mark; its RTS returns to readSectorFromDisk"
  },
  "039F": {
   "unit": "drive/driveFastLoader",
   "line": "two id bytes per drive"
  },
  "03A0": {
   "unit": "drive/driveFastLoader",
   "line": "index 0 = drive 0's id in zp_12/zp_13, index 2 would be drive 1's in zp_14/zp_15"
  },
  "03A1": {
   "unit": "drive/driveFastLoader",
   "line": "first disk id character, as read by the last INITIALIZE"
  },
  "03A3": {
   "unit": "drive/driveFastLoader",
   "line": "zp_16-zp_1A is the header image the ROM converter reads"
  },
  "03A5": {
   "unit": "drive/driveFastLoader",
   "line": "second disk id character"
  },
  "03A7": {
   "unit": "drive/driveFastLoader",
   "line": "id byte 2 of the image"
  },
  "03A9": {
   "unit": "drive/driveFastLoader",
   "line": "offset 0 of the header table entry"
  },
  "03AB": {
   "unit": "drive/driveFastLoader",
   "line": "the controller left zp_32/zp_33 pointing at this buffer's header table entry ($0006), so this is the track"
  },
  "03AD": {
   "unit": "drive/driveFastLoader",
   "line": "track of the header to look for"
  },
  "03AF": {
   "unit": "drive/driveFastLoader",
   "line": "offset 1 of the same entry"
  },
  "03B0": {
   "unit": "drive/driveFastLoader",
   "line": "and this is the sector"
  },
  "03B2": {
   "unit": "drive/driveFastLoader",
   "line": "sector of the header to look for"
  },
  "03B4": {
   "unit": "drive/driveFastLoader",
   "line": "build the header checksum from scratch"
  },
  "03B6": {
   "unit": "drive/driveFastLoader",
   "line": "id character 1"
  },
  "03B8": {
   "unit": "drive/driveFastLoader",
   "line": "id character 2"
  },
  "03BA": {
   "unit": "drive/driveFastLoader",
   "line": "track"
  },
  "03BC": {
   "unit": "drive/driveFastLoader",
   "line": "sector - DOS stores exactly this XOR in the header"
  },
  "03BE": {
   "unit": "drive/driveFastLoader",
   "line": "expected header checksum"
  },
  "03C0": {
   "unit": "drive/driveFastLoader",
   "line": "$F934 packs the header block (block id $08, the checksum, sector, track, both id characters and two $0F gap bytes) into ten GCR bytes at zp_24-zp_2D"
  },
  "03C3": {
   "unit": "drive/driveFastLoader",
   "line": "$5A = 90 headers to inspect, about four disk revolutions, before declaring the sector missing"
  },
  "03C8": {
   "unit": "drive/driveFastLoader",
   "line": "compare from the first GCR byte of the image"
  },
  "03CA": {
   "unit": "drive/driveFastLoader",
   "line": "wait for BYTE READY (SO sets V): the head has assembled another GCR byte"
  },
  "03CC": {
   "unit": "drive/driveFastLoader",
   "line": "clear V so the next byte can set it"
  },
  "03CD": {
   "unit": "drive/driveFastLoader",
   "line": "the raw GCR byte, straight off the read head"
  },
  "03D0": {
   "unit": "drive/driveFastLoader",
   "line": "compare with the expected GCR image (absolute,Y - the 6502 has no zero page,Y form of CMP)"
  },
  "03D3": {
   "unit": "drive/driveFastLoader",
   "line": "one byte wrong means this is not our header"
  },
  "03D5": {
   "unit": "drive/driveFastLoader",
   "line": "next GCR byte"
  },
  "03D6": {
   "unit": "drive/driveFastLoader",
   "line": "eight of the ten GCR bytes are enough: that covers block id, checksum, sector, track and both id characters"
  },
  "03D8": {
   "unit": "drive/driveFastLoader",
   "line": "keep comparing"
  },
  "03DA": {
   "unit": "drive/driveFastLoader",
   "line": "matched - the head is now just behind this sector's header"
  },
  "03DB": {
   "unit": "drive/driveFastLoader",
   "line": "one try used up"
  },
  "03DC": {
   "unit": "drive/driveFastLoader",
   "line": "keep looking while tries remain"
  },
  "03DE": {
   "unit": "drive/driveFastLoader",
   "line": "DOS error 2 = header block not found (READ ERROR 20)"
  },
  "03E0": {
   "unit": "drive/driveFastLoader",
   "line": "shared error exit; waitForSyncByte's timeout branches in here with A = $03 instead"
  },
  "03E5": {
   "unit": "drive/driveFastLoader",
   "line": "writing T1C-H starts VIA1 timer 1 counting down from $D0xx at 1 MHz"
  },
  "03E8": {
   "unit": "drive/driveFastLoader",
   "line": "preload the timeout status code: DOS error 3 = no sync (READ ERROR 21).  Nothing in the loop below touches A, so it is still $03 at the error exit"
  },
  "03ED": {
   "unit": "drive/driveFastLoader",
   "line": "high byte has fallen below $80, about 20 ms with no sync at all: give up with A = $03"
  },
  "03EF": {
   "unit": "drive/driveFastLoader",
   "line": "bit 7 of the disk control port is the SYNC input, low while a sync mark is under the head"
  },
  "03F2": {
   "unit": "drive/driveFastLoader",
   "line": "still no sync - back to watching the timer"
  },
  "03F4": {
   "unit": "drive/driveFastLoader",
   "line": "dummy read of the data port: it clears the pending BYTE READY so the next one is the first byte after the sync"
  },
  "03F7": {
   "unit": "drive/driveFastLoader",
   "line": "start the caller's wait with V clear"
  },
  "03F8": {
   "unit": "drive/driveFastLoader",
   "line": "callers index their buffers from 0"
  },
  "03FA": {
   "unit": "drive/driveFastLoader",
   "line": "back with the head just inside the sync mark"
  },
  "03FC": {
   "unit": "drive/driveFastLoader",
   "line": "$B0 = DOS job code SEEK"
  },
  "03FE": {
   "unit": "drive/driveFastLoader",
   "line": "ask the controller to step the head to the track now in zp_06 and read a header there"
  },
  "0400": {
   "unit": "drive/driveFastLoader",
   "line": "poll buffer 0's job byte"
  },
  "0402": {
   "unit": "drive/driveFastLoader",
   "line": "wait while bit 7 (job pending) is set; the result code is not looked at"
  },
  "0404": {
   "unit": "drive/driveFastLoader",
   "line": "from here on this code drives the disk hardware itself - no controller interrupt may disturb the byte-ready timing"
  },
  "0405": {
   "unit": "drive/driveFastLoader",
   "line": "enable BYTE READY -> SO, and refuse to go on unless the disk id is OZ"
  },
  "0408": {
   "unit": "drive/driveFastLoader",
   "line": "high byte of buffer 3"
  },
  "040A": {
   "unit": "drive/driveFastLoader",
   "line": "retarget the ROM helpers' buffer pointer zp_30/zp_31 from buffer 0 ($0300 - which holds this very code) to $0600; the low byte the controller left there is already 0"
  },
  "040C": {
   "unit": "drive/driveFastLoader",
   "line": "wait for this sector's header, then for the data field's sync mark"
  },
  "0411": {
   "unit": "drive/driveFastLoader",
   "line": "re-arm the byte-ready flag"
  },
  "0412": {
   "unit": "drive/driveFastLoader",
   "line": "raw GCR byte off the head"
  },
  "0415": {
   "unit": "drive/driveFastLoader",
   "line": "store it in buffer 3 (Y came back as 0 from waitForSyncByte)"
  },
  "0418": {
   "unit": "drive/driveFastLoader",
   "line": "next byte"
  },
  "0419": {
   "unit": "drive/driveFastLoader",
   "line": "256 GCR bytes fill the buffer"
  },
  "041B": {
   "unit": "drive/driveFastLoader",
   "line": "the remaining 70 GCR bytes of the data field go to $01BA-$01FF, above the stack area"
  },
  "041D": {
   "unit": "drive/driveFastLoader",
   "line": "wait for the next byte"
  },
  "041F": {
   "unit": "drive/driveFastLoader",
   "line": "re-arm"
  },
  "0420": {
   "unit": "drive/driveFastLoader",
   "line": "raw GCR byte"
  },
  "0423": {
   "unit": "drive/driveFastLoader",
   "line": "into the overflow area"
  },
  "0426": {
   "unit": "drive/driveFastLoader",
   "line": "next"
  },
  "0427": {
   "unit": "drive/driveFastLoader",
   "line": "until Y wraps past $01FF - 326 GCR bytes in all"
  },
  "042C": {
   "unit": "drive/driveFastLoader",
   "line": "$F5E9 EORs the 256 bytes at (zp_30) together"
  },
  "042F": {
   "unit": "drive/driveFastLoader",
   "line": "compare with the checksum that was stored in the sector"
  },
  "0431": {
   "unit": "drive/driveFastLoader",
   "line": "sector is good"
  },
  "0433": {
   "unit": "drive/driveFastLoader",
   "line": "DOS error 5 = data block checksum error (READ ERROR 23)"
  },
  "0435": {
   "unit": "drive/driveFastLoader",
   "line": "report it and go back to the command loop"
  },
  "0438": {
   "unit": "drive/driveFastLoader",
   "line": "decrypt the buffer in place; its shared exit RTSes to commandDispatchLoop for us"
  },
  "044D": {
   "unit": "drive/driveFastLoader",
   "line": "pull CLK low: 'the sector is ready'.  The C64 waits for exactly this at $0929 / $C0C8"
  },
  "0450": {
   "unit": "drive/driveFastLoader",
   "line": "start at the first byte of the buffer"
  },
  "0455": {
   "unit": "drive/driveFastLoader",
   "line": "shift the high nibble down (1 of 4)"
  },
  "0456": {
   "unit": "drive/driveFastLoader",
   "line": "(2 of 4)"
  },
  "0457": {
   "unit": "drive/driveFastLoader",
   "line": "(3 of 4)"
  },
  "0458": {
   "unit": "drive/driveFastLoader",
   "line": "(4 of 4) - A is now the high nibble"
  },
  "0459": {
   "unit": "drive/driveFastLoader",
   "line": "park it on the stack for the second half of the byte"
  },
  "045A": {
   "unit": "drive/driveFastLoader",
   "line": "fetch the byte again"
  },
  "045D": {
   "unit": "drive/driveFastLoader",
   "line": "the low nibble travels first"
  },
  "045F": {
   "unit": "drive/driveFastLoader",
   "line": "index the encoding table with it"
  },
  "0460": {
   "unit": "drive/driveFastLoader",
   "line": "the two port values for this nibble, packed into one byte"
  },
  "0463": {
   "unit": "drive/driveFastLoader",
   "line": "hold the encoded low nibble in X"
  },
  "0464": {
   "unit": "drive/driveFastLoader",
   "line": "bit 0 is an input, so this value releases both CLK OUT and DATA OUT - and it doubles as the DATA IN mask for the BIT below"
  },
  "0466": {
   "unit": "drive/driveFastLoader",
   "line": "let both lines go so the host can pull DATA"
  },
  "0469": {
   "unit": "drive/driveFastLoader",
   "line": "Z = 0 while DATA IN (bit 0) is set, i.e. while the host is holding DATA low"
  },
  "046C": {
   "unit": "drive/driveFastLoader",
   "line": "wait until the C64 releases DATA - its 'stx CIA2_PRA' at $0966 / $C105 asking for a byte"
  },
  "046E": {
   "unit": "drive/driveFastLoader",
   "line": "write 1: encoded bits 3 and 1 put data bits 1 and 0 on CLK and DATA"
  },
  "0471": {
   "unit": "drive/driveFastLoader",
   "line": "the same encoded nibble again"
  },
  "0472": {
   "unit": "drive/driveFastLoader",
   "line": "shift its other two bits into the CLK/DATA positions"
  },
  "0473": {
   "unit": "drive/driveFastLoader",
   "line": "keep only the four port bits (drop what fell into bit 4)"
  },
  "0475": {
   "unit": "drive/driveFastLoader",
   "line": "write 2: data bits 3 and 2"
  },
  "0478": {
   "unit": "drive/driveFastLoader",
   "line": "the high nibble saved above"
  },
  "0479": {
   "unit": "drive/driveFastLoader",
   "line": "index the table with it"
  },
  "047A": {
   "unit": "drive/driveFastLoader",
   "line": "its two port values"
  },
  "047D": {
   "unit": "drive/driveFastLoader",
   "line": "write 3: data bits 5 and 4"
  },
  "0480": {
   "unit": "drive/driveFastLoader",
   "line": "shift the remaining two bits into place"
  },
  "0481": {
   "unit": "drive/driveFastLoader",
   "line": "mask to the four port bits"
  },
  "0483": {
   "unit": "drive/driveFastLoader",
   "line": "two cycles of padding so write 4 lands where the host samples it"
  },
  "0484": {
   "unit": "drive/driveFastLoader",
   "line": "write 4: data bits 7 and 6 - the byte is gone"
  },
  "0487": {
   "unit": "drive/driveFastLoader",
   "line": "next buffer byte"
  },
  "0488": {
   "unit": "drive/driveFastLoader",
   "line": "all 256 of them"
  },
  "048A": {
   "unit": "drive/driveFastLoader",
   "line": "$08 = CLK OUT again: park the bus with CLK low, the state exchangeByteWithHost starts from"
  },
  "048C": {
   "unit": "drive/driveFastLoader",
   "line": "padding so the last write is clear of the host's final sample"
  },
  "048D": {
   "unit": "drive/driveFastLoader",
   "line": "the store itself lives at $05DE, out in the third loader sector"
  },
  "0493": {
   "unit": "drive/driveFastLoader",
   "line": "track byte of buffer 0's header table entry"
  },
  "0495": {
   "unit": "drive/driveFastLoader",
   "line": "receive the sector number"
  },
  "0498": {
   "unit": "drive/driveFastLoader",
   "line": "sector byte of the same entry"
  },
  "049A": {
   "unit": "drive/driveFastLoader",
   "line": "receive 256 bytes, encrypt them and write the sector; it sends its own status byte"
  },
  "049D": {
   "unit": "drive/driveFastLoader",
   "line": "back for the next command"
  },
  "04A2": {
   "unit": "drive/driveFastLoader",
   "line": "step the head to the track in zp_06"
  },
  "04A4": {
   "unit": "drive/driveFastLoader",
   "line": "poll buffer 0's job byte"
  },
  "04A6": {
   "unit": "drive/driveFastLoader",
   "line": "wait for the controller to finish the seek"
  },
  "04A8": {
   "unit": "drive/driveFastLoader",
   "line": "take the disk hardware over: the timing below is ours"
  },
  "04A9": {
   "unit": "drive/driveFastLoader",
   "line": "enable BYTE READY -> SO and check the disk id"
  },
  "04AC": {
   "unit": "drive/driveFastLoader",
   "line": "fill the buffer from the start"
  },
  "04B1": {
   "unit": "drive/driveFastLoader",
   "line": "into buffer 3"
  },
  "04B4": {
   "unit": "drive/driveFastLoader",
   "line": "next"
  },
  "04B5": {
   "unit": "drive/driveFastLoader",
   "line": "256 bytes"
  },
  "04B7": {
   "unit": "drive/driveFastLoader",
   "line": "encrypt in place - the very same routine that decrypts on a read"
  },
  "04BA": {
   "unit": "drive/driveFastLoader",
   "line": "high byte of buffer 3"
  },
  "04BC": {
   "unit": "drive/driveFastLoader",
   "line": "point the ROM helpers' buffer pointer at $0600"
  },
  "04BE": {
   "unit": "drive/driveFastLoader",
   "line": "$F5E9 EORs the 256 bytes together"
  },
  "04C1": {
   "unit": "drive/driveFastLoader",
   "line": "zp_3A is where the GCR encoder picks up the data block checksum"
  },
  "04C6": {
   "unit": "drive/driveFastLoader",
   "line": "bit 4 = write protect sense; it reads 0 when the notch is covered"
  },
  "04C8": {
   "unit": "drive/driveFastLoader",
   "line": "not protected - go ahead"
  },
  "04CA": {
   "unit": "drive/driveFastLoader",
   "line": "DOS error 8 = write protect on (error 26); prepareHeadAndVerifyId reuses this exit for a wrong disk id"
  },
  "04CC": {
   "unit": "drive/driveFastLoader",
   "line": "report it and abandon the write"
  },
  "04D2": {
   "unit": "drive/driveFastLoader",
   "line": "wait until this sector's header goes by"
  },
  "04D5": {
   "unit": "drive/driveFastLoader",
   "line": "nine byte times of header gap between the header and the data field"
  },
  "04D7": {
   "unit": "drive/driveFastLoader",
   "line": "wait one byte time"
  },
  "04D9": {
   "unit": "drive/driveFastLoader",
   "line": "re-arm"
  },
  "04DA": {
   "unit": "drive/driveFastLoader",
   "line": "one gap byte gone"
  },
  "04DB": {
   "unit": "drive/driveFastLoader",
   "line": "keep skipping"
  },
  "04DF": {
   "unit": "drive/driveFastLoader",
   "line": "port A now feeds the write shift register instead of reading it"
  },
  "04E2": {
   "unit": "drive/driveFastLoader",
   "line": "peripheral control register"
  },
  "04E5": {
   "unit": "drive/driveFastLoader",
   "line": "clear the CB2 control bits"
  },
  "04E7": {
   "unit": "drive/driveFastLoader",
   "line": "CB2 = manual output low = write mode (the DOS $FE00 sequence)"
  },
  "04E9": {
   "unit": "drive/driveFastLoader",
   "line": "the head amplifier is now recording"
  },
  "04EC": {
   "unit": "drive/driveFastLoader",
   "line": "$FF written continuously is a SYNC mark"
  },
  "04EE": {
   "unit": "drive/driveFastLoader",
   "line": "five sync bytes ahead of the data field"
  },
  "04F0": {
   "unit": "drive/driveFastLoader",
   "line": "hand the first one to the write shift register"
  },
  "04F3": {
   "unit": "drive/driveFastLoader",
   "line": "clear V before timing the rest"
  },
  "04F4": {
   "unit": "drive/driveFastLoader",
   "line": "wait one byte time"
  },
  "04F6": {
   "unit": "drive/driveFastLoader",
   "line": "re-arm"
  },
  "04F7": {
   "unit": "drive/driveFastLoader",
   "line": "one sync byte written"
  },
  "04F8": {
   "unit": "drive/driveFastLoader",
   "line": "the latched $FF keeps going out, so this produces five of them"
  },
  "04FC": {
   "unit": "drive/driveFastLoader",
   "line": "next GCR byte"
  },
  "04FF": {
   "unit": "drive/driveFastLoader",
   "line": "wait until the head has taken the previous one"
  },
  "0501": {
   "unit": "drive/driveFastLoader",
   "line": "re-arm"
  },
  "0502": {
   "unit": "drive/driveFastLoader",
   "line": "hand it to the write shift register"
  },
  "0505": {
   "unit": "drive/driveFastLoader",
   "line": "next"
  },
  "0506": {
   "unit": "drive/driveFastLoader",
   "line": "through $01FF"
  },
  "0508": {
   "unit": "drive/driveFastLoader",
   "line": "then the 256 GCR bytes in buffer 3 (Y has wrapped to 0)"
  },
  "050A": {
   "unit": "drive/driveFastLoader",
   "line": "wait for the head"
  },
  "050C": {
   "unit": "drive/driveFastLoader",
   "line": "re-arm"
  },
  "050D": {
   "unit": "drive/driveFastLoader",
   "line": "write it"
  },
  "0510": {
   "unit": "drive/driveFastLoader",
   "line": "next"
  },
  "0511": {
   "unit": "drive/driveFastLoader",
   "line": "all 325 GCR bytes are now on the disk"
  },
  "0515": {
   "unit": "drive/driveFastLoader",
   "line": "peripheral control register"
  },
  "0518": {
   "unit": "drive/driveFastLoader",
   "line": "flip the CB2 control bits back out of 'manual output low', so the head stops recording"
  },
  "051A": {
   "unit": "drive/driveFastLoader",
   "line": "read mode again"
  },
  "051D": {
   "unit": "drive/driveFastLoader",
   "line": "A = 0 here, which is also the status byte sent below"
  },
  "051F": {
   "unit": "drive/driveFastLoader",
   "line": "port A back to input"
  },
  "0522": {
   "unit": "drive/driveFastLoader",
   "line": "send status 0 = ok; its RTS returns to handleWriteCommand"
  },
  "0528": {
   "unit": "drive/driveFastLoader",
   "line": "first disk id character ('O' on the original)"
  },
  "052A": {
   "unit": "drive/driveFastLoader",
   "line": "second one ('Z')"
  },
  "052C": {
   "unit": "drive/driveFastLoader",
   "line": "$4F EOR $5A = $15, so this comes out 0 only for an OZ disk"
  },
  "052E": {
   "unit": "drive/driveFastLoader",
   "line": "wrong disk: report $08 and let the host spin on its red border"
  },
  "0530": {
   "unit": "drive/driveFastLoader",
   "line": "right disk - carry on with the read or write"
  },
  "0533": {
   "unit": "drive/driveFastLoader",
   "line": "$12 = 18, the directory track"
  },
  "0535": {
   "unit": "drive/driveFastLoader",
   "line": "track 18 is stored in the clear - skip the cipher entirely"
  },
  "0537": {
   "unit": "drive/driveFastLoader",
   "line": "force bits 6 and 7 so the shift below always leaves carry set"
  },
  "0539": {
   "unit": "drive/driveFastLoader",
   "line": "(track OR $C0) * 2, carry = 1"
  },
  "053A": {
   "unit": "drive/driveFastLoader",
   "line": "plus the sector and that carry; the carry out of this add feeds the first ROL"
  },
  "053C": {
   "unit": "drive/driveFastLoader",
   "line": "start at the first buffer byte"
  },
  "053E": {
   "unit": "drive/driveFastLoader",
   "line": "always taken (Y was just set to 0): the first key byte comes straight from the seed"
  },
  "0541": {
   "unit": "drive/driveFastLoader",
   "line": "EOR with the previous key byte - the operand at $0542 is rewritten below (self-modifying)"
  },
  "0543": {
   "unit": "drive/driveFastLoader",
   "line": "rotate left, taking in the carry from the previous round"
  },
  "0544": {
   "unit": "drive/driveFastLoader",
   "line": "that is the new key byte; keep it in the EOR operand at $0542 for the next round"
  },
  "0547": {
   "unit": "drive/driveFastLoader",
   "line": "XOR the buffer byte with the key"
  },
  "054A": {
   "unit": "drive/driveFastLoader",
   "line": "in place: decrypts after a read, encrypts before a write"
  },
  "054D": {
   "unit": "drive/driveFastLoader",
   "line": "next byte"
  },
  "054E": {
   "unit": "drive/driveFastLoader",
   "line": "all 256 of them"
  },
  "0550": {
   "unit": "drive/driveFastLoader",
   "line": "force the branch below"
  },
  "0551": {
   "unit": "drive/driveFastLoader",
   "line": "always taken: leave through the shared exit, which reloads A with the last command byte and RTSes"
  },
  "0556": {
   "unit": "drive/driveFastLoader",
   "line": "store it - this instruction's own operand starts at $0580 (the BEQ offset in receiveCommand's tail) and walks upward"
  },
  "0559": {
   "unit": "drive/driveFastLoader",
   "line": "bump the destination low byte at $0557 for the next byte"
  },
  "055C": {
   "unit": "drive/driveFastLoader",
   "line": "count the byte counter at $057E up (it started at $F0-$FF)"
  },
  "055F": {
   "unit": "drive/driveFastLoader",
   "line": "keep receiving until the counter wraps to 0, then fall into receiveCommand for the real command"
  },
  "0564": {
   "unit": "drive/driveFastLoader",
   "line": "clear bit 3 = drive LED off while the drive is idle"
  },
  "0566": {
   "unit": "drive/driveFastLoader",
   "line": "read-modify-write, so the motor and stepper bits are untouched"
  },
  "0569": {
   "unit": "drive/driveFastLoader",
   "line": "the byte handed back to the host during the exchange; it ignores what it gets here and only checks the status exchange"
  },
  "056B": {
   "unit": "drive/driveFastLoader",
   "line": "receive the command byte"
  },
  "056E": {
   "unit": "drive/driveFastLoader",
   "line": "keep it in the LDA operand at $057E, which is also the upload counter"
  },
  "0571": {
   "unit": "drive/driveFastLoader",
   "line": "$F0-$FF is not a command but 'here come 256 minus this many bytes for your RAM'"
  },
  "0573": {
   "unit": "drive/driveFastLoader",
   "line": "go and swallow them, then come back here for the real command"
  },
  "0575": {
   "unit": "drive/driveFastLoader",
   "line": "disk control port again"
  },
  "0578": {
   "unit": "drive/driveFastLoader",
   "line": "bit 3 = drive LED on for the duration of the command"
  },
  "057A": {
   "unit": "drive/driveFastLoader",
   "line": "write it back"
  },
  "057D": {
   "unit": "drive/driveFastLoader",
   "line": "operand at $057E is patched with the byte just received (or with the count during an upload); cryptSectorBuffer also exits through here"
  },
  "057F": {
   "unit": "drive/driveFastLoader",
   "line": "a branch to the next instruction as it stands - but its operand at $0580 is the first byte an upload overwrites, so after an upload this is where the injected code starts"
  },
  "0581": {
   "unit": "drive/driveFastLoader",
   "line": "shared exit, also used by cryptSectorBuffer; the second byte of an upload overwrites it"
  },
  "05E1": {
   "unit": "drive/driveFastLoader",
   "line": "back to commandDispatchLoop, which then sends the status byte"
  }
 },
 "dataTypes": {
  "043B": [
   "byte",
   16
  ],
  "0582": [
   "byte",
   92
  ],
  "05E2": [
   "byte",
   18
  ],
  "05F4": [
   "byte",
   12
  ]
 }
}