A comprehensive document about the CBM ROM Loader
CBM ROM Loader
Informations provided by Luigi Di Fraia (armaeth@libero.it) as a guide to the use of the Exam20 tool, by the same author, with TAP files using CBM ROM Loader. These informations were previously available as part of the TZX project and SubChrist's FinalTap documentation, but they were adapted to describe Exam20's internal scanner. Therefore, they should be taken as a guideline to how Exam20 works, and not as the original standard specifications.
Pulse types
3 different pulse types were defined by CBM as standard for ROM Loader:
(S)hort : $2B (2840 Hz)
(M)edium : $3F (1953 Hz)
(L)ong : $53 (1488 Hz)
anyway, the following values are more likely to be seen on C64 tapes and few VIC20 ones:
(S)hort : $30
(M)edium : $42
(L)ong : $56
Pulses are always interpreted as a pair:
(S,M) = 0 bit
(M,S) = 1 bit
(L,M) = new-data marker
(L,S) = end-of-data marker
Framing
Each data byte is organized as follows:
(?,?) (?,?) (?,?) (?,?) (?,?) (?,?) (?,?) (?,?) (?,?) (?,?)
| | | | | | | | | |
bit0 bit1 bit2 bit3 bit4 bit5 bit6 bit7 | |
| |
checkbit |
data marker
So that, each byte is encoded as a sequence of 20 pulses (10 pairs):
- 8 bits of information in LSbF format.
- 1 checkbit which is computed as:
- 1 XOR bit0 XOR bit1 XOR bit2 XOR bit3 XOR bit4 XOR bit5 XOR bit6 XOR bit7.
- 1 data marker:
- the data is finished when you read an "end-of-data marker" (L,S) instead of (L,M).
Structure
When a VIC20 or a C64 save a "BASIC program" or a "PRG file" to tape with the following:
SAVE "MY PROGRAM", 1 (BASIC program)
SAVE "MY PROGRAM", 1, 1 (PRG file)
they create 4 files:
HEADER
HEADER REPEATED
silence (roughly 0.4 seconds)
DATA
DATA REPEATED
When a SEQuential file is saved to tape with:
OPEN N, 1, 1, "MY SEQ DATA" (no "End-of-tape marker" is saved)
OPEN N, 1, 2, "MY SEQ DATA" ("End-of-tape marker" is saved after all files)
PRINT# N, "DATA STARTS HERE..."
...
PRINT# N, "... DATA..."
...
PRINT# N, "STOP"
CLOSE N
it's segmented, if required, and encapsulated into HEADER files. A padding is automatically done, if required, since HEADER payload has a standard length (191 bytes). An empty HEADER (all "File name" and "body" bytes are $20) comes before data:
HEADER - SEQ file header
HEADER - SEQ file header, REPEATED
One or more of these follow, depending on the SEQ data size:
silence (duration is variable)
HEADER - Data block for SEQ file
HEADER - Data block for SEQ file, REPEATED
If an "End-of-tape marker" is requested, the OS saves an additional empty HEADER just after the last "Data block for SEQ file":
silence (roughly 0.34 seconds)
HEADER - End-of-tape marker
HEADER - End-of-tape marker, REPEATED
Pilot
Pilot: various amounts of 'S' pulses.
The amount of pulses is:
- $6A00 for HEADER
- $1500 for DATA, and HEADER when it contains "Data block for SEQ file"
- $4F for HEADER REPEATED and DATA REPEATED
Sync
Sync: a new-data marker, followed by a sync train (9 bytes):
Both HEADER and DATA blocks have the following sequence:
$89 $88 $87 $86 $85 $84 $83 $82 $81
Both HEADER REPEATED and DATA REPEATED blocks have the same sequence with bit 7 clear:
$09 $08 $07 $06 $05 $04 $03 $02 $01
HEADER
For any HEADER the following information is sent after the sync sequence:
1 Byte : File type.
$01= BASIC program
$02= Data block for SEQ file
$03= PRG file
$04= SEQ file header
$05= End-of-tape marker
Here starts what I refer to as HEADER "payload".
In case File type is not $02, the following informations are sent too:
2 Bytes : Start Address (LSBF).
2 Bytes : End Address+1 (LSBF).
16 Bytes : File Name.
When File type is $02, SEQ file data starts immediately after File Type thus allowing the use of those 20 bytes to store additional data.
After the File Name there is HEADER "body", 171 bytes, often used by commercial loaders to store executable loader code or pretty much any additional data or code the loader or program may require.
It encapsulates Data for segmented SEQ files too, as discussed before.
Last Byte: Data checkbyte (its data marker is "end-of-data marker"), computed as:
0 XOR all other HEADER bytes, from "File type" to end of "body".
DATA
For any DATA the following information is sent after the sync sequence:
DATA body
Last Byte: Data checkbyte (its data marker is "end-of-data marker"), computed as:
0 XOR all DATA "body" bytes.
Trailer
Some trailing tones ('S' pulses) follow both HEADER REPEATED and DATA REPEATED.
The standard amount is $4E pulses.
C64 Notes
HEADER blocks always load into the Tape Buffer at $033C.
If the File Type is 'BASIC Program' the start address for loading will be $0801 regardless of what may be written in the 'Start Address' field.
How the 64 rom loader code looks
;[Generated by 6510 Dasm v2.1b ©2004-05 Luigi Di Fraia]
;
;load RAM from a device
;
JF49E 86 C3 STX $C3 ;set destination address from XY
84 C4 STY $C4
6C 30 03 JMP ($0330) ;load RAM (normally F4A5)
;
;standard load RAM entry
;
WF4A5 85 93 STA $93 ;set load/verify switch to load
A9 00 LDA #$00
85 90 STA $90 ;clear ST
A5 BA LDA $BA ;if current device is keyboard (0)
D0 03 BNE $F4B2
BF4AF 4C 13 F7 JMP $F713 ;indicate Illegal Device # Error
BF4B2 C9 03 CMP #$03 ;if current device is the screen
F0 F9 BEQ $F4AF ;indicate error
90 7B BCC $F533 ;if not serial bus device
A4 B7 LDY $B7 ;and if no filename,
D0 03 BNE $F4BF
4C 10 F7 JMP $F710 ;indicate File Name Missing Error
BF4BF A6 B9 LDX $B9 ;move X to secondary address
20 AF F5 JSR $F5AF ;handle load messages
A9 60 LDA #$60 ;set current secondary address
85 B9 STA $B9
20 D5 F3 JSR $F3D5 ;perform open of serial bus device
A5 BA LDA $BA ;let A = current device
20 09 ED JSR $ED09 ;send TALK on serial bus
A5 B9 LDA $B9 ;fetch secondary address
20 C7 ED JSR $EDC7 ;and send on serial bus
20 13 EE JSR $EE13 ;input a byte on serial bus
85 AE STA $AE ;set I/O end address
A5 90 LDA $90
4A LSR
4A LSR
B0 50 BCS $F530 ;if ST doesn't indicate a timeout (read)
20 13 EE JSR $EE13 ;input a byte on serial bus
85 AF STA $AF ;set high byte of end address
8A TXA
D0 08 BNE $F4F0 ;if EOI is not low,
A5 C3 LDA $C3 ;use destination address
85 AE STA $AE ;as end address
A5 C4 LDA $C4 ;ditto for high byte
85 AF STA $AF
BF4F0 20 D2 F5 JSR $F5D2 ;print LOAD or VERIFY
BF4F3 A9 FD LDA #$FD ;clear timeout (read) bit
25 90 AND $90 ;in ST
85 90 STA $90
20 E1 FF JSR $FFE1 ;check for Stop key
D0 03 BNE $F501 ;if depressed
4C 33 F6 JMP $F633 ;abort load
BF501 20 13 EE JSR $EE13 ;input a byte on serial bus
AA TAX
A5 90 LDA $90 ;if Timeout (read) set in ST
4A LSR
4A LSR
B0 E8 BCS $F4F3 ;abort load
8A TXA
A4 93 LDY $93 ;if in verify mode
F0 0C BEQ $F51C
A0 00 LDY #$00
D1 AE CMP ($AE),Y ;compare byte read to memory
F0 08 BEQ $F51E
A9 10 LDA #$10
20 1C FE JSR $FE1C ;and set verify error on mismatch
2C .BYTE $2C ;skip next instruction
BF51C 91 AE STA ($AE),Y ;load byte to memory
BF51E E6 AE INC $AE ;bump load address
D0 02 BNE $F524
E6 AF INC $AF
BF524 24 90 BIT $90 ;if not end of file
50 CB BVC $F4F3 ;repeat
20 EF ED JSR $EDEF ;else send TALK on serial bus
20 42 F6 JSR $F642 ;close serial bus
90 79 BCC $F5A9 ;and exit
BF530 4C 04 F7 JMP $F704 ;indicate File Not Found Error
BF533 4A LSR ;if input device is not 1 (cassette)
B0 03 BCS $F539
4C 13 F7 JMP $F713 ;indicate Illegal Device #
BF539 20 D0 F7 JSR $F7D0 ;fetch tape buffer pointer
B0 03 BCS $F541
4C 13 F7 JMP $F713 ;if invalid, indicate Illegal Device #
BF541 20 17 F8 JSR $F817 ;display msgs and test buttons for read
B0 68 BCS $F5AE
20 AF F5 JSR $F5AF ;handle load messages
BF549 A5 B7 LDA $B7 ;if file name present
F0 09 BEQ $F556
20 EA F7 JSR $F7EA ;search tape for file name
90 0B BCC $F55D ;if no errors, continue
F0 5A BEQ $F5AE ;exit if end of tape
B0 DA BCS $F530 ;error if not found
BF556 20 2C F7 JSR $F72C ;since no file name, get next tape hdr
F0 53 BEQ $F5AE ;exit if end of tape found
B0 D3 BCS $F530 ;indicate File Not found Error
BF55D A5 90 LDA $90 ;check ST for unrecoverable read error
29 10 AND #$10
38 SEC
D0 4A BNE $F5AE ;and exit if so
E0 01 CPX #$01 ;if not Program Header
F0 11 BEQ $F579
E0 03 CPX #$03
D0 DD BNE $F549
BF56C A0 01 LDY #$01
B1 B2 LDA ($B2),Y
85 C3 STA $C3 ;reset load address from tape buffer
C8 INY
B1 B2 LDA ($B2),Y ;high byte also
85 C4 STA $C4
B0 04 BCS $F57D
BF579 A5 B9 LDA $B9
D0 EF BNE $F56C
BF57D A0 03 LDY #$03 ;index low byte of end address
B1 B2 LDA ($B2),Y
A0 01 LDY #$01
F1 B2 SBC ($B2),Y ;compute length of block to load
AA TAX
A0 04 LDY #$04
B1 B2 LDA ($B2),Y
A0 02 LDY #$02
F1 B2 SBC ($B2),Y
A8 TAY
18 CLC
8A TXA
65 C3 ADC $C3
85 AE STA $AE ;and set the end address of I/O area
98 TYA
65 C4 ADC $C4
85 AF STA $AF
A5 C3 LDA $C3
85 C1 STA $C1 ;set tape load address
A5 C4 LDA $C4
85 C2 STA $C2
20 D2 F5 JSR $F5D2 ;display load messages
20 4A F8 JSR $F84A ;load from cassette
24 .BYTE $24 ;skip next instruction
BF5A9 18 CLC ;clear error flag
A6 AE LDX $AE ;exit with end address in XY
A4 AF LDY $AF
BF5AE 60 RTS
;---------------------------------
;
;get next file header from cassette
;
SF72C A5 93 LDA $93 ;save load/verify switch on stack
48 PHA
20 41 F8 JSR $F841 ;read a block from tape
68 PLA
85 93 STA $93 ;restore load/verify flag
B0 32 BCS $F769 ;exit if read error
A0 00 LDY #$00
B1 B2 LDA ($B2),Y ;get first character in tape buffer
C9 05 CMP #$05 ;if code for End of Tape
F0 2A BEQ $F769 ;return
C9 01 CMP #$01
F0 08 BEQ $F74B ;if not code for Program Heafer
C9 03 CMP #$03 ;or "?"
F0 04 BEQ $F74B
C9 04 CMP #$04
D0 E1 BNE $F72C ;or Data Header, try next block
BF74B AA TAX
24 9D BIT $9D ;if in direct mode,
10 17 BPL $F767
A0 63 LDY #$63 ;point to message FOUND
20 2F F1 JSR $F12F ;and print it
A0 05 LDY #$05
BF757 B1 B2 LDA ($B2),Y
20 D2 FF JSR $FFD2 ;print a file name character
C8 INY
C0 15 CPY #$15 ;and repeat
D0 F6 BNE $F757 ;for all characters
A5 A1 LDA $A1
20 E0 E4 JSR $E4E0 ;pause
EA NOP ;filler for patch
18 CLC
88 DEY
60 RTS
;---------------------------------
;
;read a block from cassette
;
SF841 A9 00 LDA #$00
85 90 STA $90 ;clear ST
85 93 STA $93 ;set load/verify switch to load
20 D7 F7 JSR $F7D7 ;set tape buffer to I/O area
SF84A 20 17 F8 JSR $F817 ;handle msgs and test sense for read
B0 1F BCS $F86E
78 SEI ;disable IRQ
A9 00 LDA #$00
85 AA STA $AA ;set gap
85 B4 STA $B4 ;set no sync estabilished
85 B0 STA $B0 ;set no special speed correction yet
85 9E STA $9E ;initialize error log index for pass 1
85 9F STA $9F ;and pass2
85 9C STA $9C ;set no byte available yet
A9 90 LDA #$90 ;set Flag mask
A2 0E LDX #$0E ;index for cassette read IRQ address
D0 11 BNE $F875 ;JMP
;
;write a block to cassette
;
SF864 20 D7 F7 JSR $F7D7 ;initialize tape buffer pointer
SF867 A9 14 LDA #$14
85 AB STA $AB ;20 sync patterns
SF86B 20 38 F8 JSR $F838 ;test sense and display msgs for output
BF86E B0 6C BCS $F8DC
78 SEI
A9 82 LDA #$82 ;mask for ICR1 to honor TB1
A2 08 LDX #$08 ;IRQ index for cassette write, part 1
;
;common code for cassette read & write
;
BF875 A0 7F LDY #$7F
8C 0D DC STY $DC0D ;clear any pending mask in ICR1
8D 0D DC STA $DC0D ;then set mask for TB1
AD 0E DC LDA $DC0E
09 19 ORA #$19 ;+force load, one shot and TB1 to CRA1
8D 0F DC STA $DC0F ;to form CRB1
29 91 AND #$91
8D A2 02 STA $02A2 ;and CRB1 activity register
20 A4 F0 JSR $F0A4 ;condition flag bit in ICR2
AD 11 D0 LDA $D011
29 EF AND #$EF
8D 11 D0 STA $D011 ;disable the screen
AD 14 03 LDA $0314 ;save standard IRQ vector
8D 9F 02 STA $029F
AD 15 03 LDA $0315
8D A0 02 STA $02A0
20 BD FC JSR $FCBD ;set new IRQ for cassette depending on X
A9 02 LDA #$02
85 BE STA $BE ;select phase 2
20 97 FB JSR $FB97 ;initialize cassette I/O variables
A5 01 LDA $01
29 1F AND #$1F
85 01 STA $01 ;start cassette motor
85 C0 STA $C0 ;set tape motor interlock
A2 FF LDX #$FF
BF8B5 A0 FF LDY #$FF
BF8B7 88 DEY
D0 FD BNE $F8B7 ;delay 0.3 seconds
CA DEX
D0 F8 BNE $F8B5
58 CLI
BF8BE AD A0 02 LDA $02A0 ;test high byte of IRQ save area
CD 15 03 CMP $0315 ;to determine if end of I/O
18 CLC
F0 15 BEQ $F8DC ;exit if so
20 D0 F8 JSR $F8D0 ;else test Stop key
20 BC F6 JSR $F6BC ;scan keyboard
4C BE F8 JMP $F8BE ;repeat
;---------------------------------
;
;set IRQ vector depending upon X
;
SFCDB BD 93 FD LDA $FD9B-8,X ;move low byte of address
8D 14 03 STA $0314 ;into low byte of IRQ vector
BD 94 FD LDA $FD9B-7,X ;then do high byte
8D 15 03 STA $0315
60 RTS
;---------------------------------
;
;IRQ vectors
;
TFD9B .WORD $FC6A
.WORD $FBDC
.WORD $EA31
.WORD $F92C
;---------------------------------
;
;cassette read IRQ routine
;
BF92C AE 07 DC LDX $DC07 ;get TBH1
A0 FF LDY #$FF
98 TYA ;and the complement of TBL1
ED 06 DC SBC $DC06 ;(time elapsed)
EC 07 DC CPX $DC07 ;if high byte not steady,
D0 F2 BNE $F92C ;repeat
86 B1 STX $B1 ;else save high byte
AA TAX
8C 06 DC STY $DC06 ;reset TBL1 to maximum
8C 07 DC STY $DC07 ;ditto TBH1
A9 19 LDA #$19 ;force load, one-shot and Timer B
8D 0F DC STA $DC0F ;into CRB1
AD 0D DC LDA $DC0D
8D A3 02 STA $02A3 ;save ICR1
98 TYA
E5 B1 SBC $B1 ;complement high byte
86 B1 STX $B1 ;save low byte
4A LSR ;elapsed time in A, ZB1
66 B1 ROR $B1 ;/ 2
4A LSR
66 B1 ROR $B1 ;/ 4
A5 B0 LDA $B0 ;get speed correction
18 CLC
69 3C ADC #$3C ;+240 microseconds
C5 B1 CMP $B1 ;if cycle shorter
B0 4A BCS $F9AC ;dismiss
A6 9C LDX $9C ;if byte available
F0 03 BEQ $F969
4C 60 FA JMP $FA60 ;receive it
BF969 A6 A3 LDX $A3 ;test bit count and if beyond last bit,
30 1B BMI $F988 ;do end of byte
A2 00 LDX #$00 ;assume bit value of 0
69 30 ADC #$30 ;add 432 microseconds
65 B0 ADC $B0 ;+ 2 * speed correction
C5 B1 CMP $B1 ;if cycle shorter
B0 1C BCS $F993 ;record a 0
E8 INX ;assume bit value of 1
69 26 ADC #$26 ;get 584 microseconds
65 B0 ADC $B0 ;+ 3 * speed correction
C5 B1 CMP $B1 ;if cycle shorter
B0 17 BCS $F997 ;record a 1
69 2C ADC #$2C ;get 760 microseconds
65 B0 ADC $B0 ;+ 4 * speed correction
C5 B1 CMP $B1 ;if cycle shorter
90 03 BCC $F98B
BF988 4C 10 FA JMP $FA10 ;go do end of byte
BF98B A5 B4 LDA $B4 ;if sync estabilished
F0 1D BEQ $F9AC
85 A8 STA $A8 ;set erroneous bits
D0 19 BNE $F9AC
BF993 E6 A9 INC $A9 ;for a 0, increment 0/1 balance
B0 02 BCS $F999
BF997 C6 A9 DEC $A9 ;for a 1, decrement 0/1 balance
BF999 38 SEC
E9 13 SBC #$13 ;0/1 cutoff level
E5 B1 SBC $B1 ;-cycle width
65 92 ADC $92
85 92 STA $92 ;accumulated for speed correction
A5 A4 LDA $A4
49 01 EOR #$01 ;flip cycle indication
85 A4 STA $A4
F0 2B BEQ $F9D5 ;if first cycle,
86 D7 STX $D7 ;save bit value
BF9AC A5 B4 LDA $B4 ;if no sync yet
F0 22 BEQ $F9D2 ;return from IRQ
AD A3 02 LDA $02A3 ;if ICR1 mask
29 01 AND #$01
D0 05 BNE $F9BC
AD A4 02 LDA $02A4 ;and last CRA1 mask shows no TA1 flag
D0 16 BNE $F9D2 ;exit from IRQ
BF9BC A9 00 LDA #$00
85 A4 STA $A4 ;clear cycle count
8D A4 02 STA $02A4 ;and last CRA1 mask
A5 A3 LDA $A3 ;if bit count indicated end of byte,
10 30 BPL $F9F7
30 BF BMI $F988 ;go do end of byte
BF9C9 A2 A6 LDX #$A6
20 E2 F8 JSR $F8E2 ;schedule timer
A5 9B LDA $9B ;if parity calculated does not match
D0 B9 BNE $F98B ;set erroneous bit flag
BF9D2 4C BC FE JMP $FEBC ;exit from IRQ
BF9D5 A5 92 LDA $92 ;if second cycle
F0 07 BEQ $F9E0 ;check accumulated over/under time
30 03 BMI $F9DE
C6 B0 DEC $B0
2C .BYTE $2C ;skip next instruction
BF9DE E6 B0 INC $E0 ;adapt speed correction accordingly
BF9E0 A9 00 LDA #$00
85 92 STA $92 ;reset accumulated over/under time
E4 D7 CPX $D7 ;if 2nd cycle = complement of cycle 1
D0 0F BNE $F9F7 ;include bit
8A TXA
D0 A0 BNE $F98B ;if two 0 cycles
A5 A9 LDA $A9 ;and 0/1 balance
30 BD BMI $F9AC
C9 10 CMP #$10 ;at least 16 "0" cycles extra
90 B9 BCC $F9AC
85 96 STA $96 ;set sync detected
B0 B5 BCS $F9AC
BF9F7 8A TXA
45 9B EOR $9B ;calculate parity
85 9B STA $9B
A5 B4 LDA $B4 ;if no sync yet
F0 D2 BEQ $F9D2 ;exit
C6 A3 DEC $A3 ;decrement pending bit count
30 C5 BMI $F9C9 ;after last bit, check parity
46 D7 LSR $D7 ;include bit
66 BF ROR $BF ;in byte being read
A2 DA LDX #$DA
20 E2 F8 JSR $F8E2 ;schedule timer
4C BC FE JMP $FEBC ;exit from IRQ
BFA10 A5 96 LDA $96 ;if sync detected
F0 04 BEQ $FA18
A5 B4 LDA $B4 ;and not yet estabilished
F0 07 BEQ $FA1F
BFA18 A5 A3 LDA $A3 ;or last bit done
30 03 BMI $FA1F
4C 97 F9 JMP $F997 ;allow byte reception
BFA1F 46 B1 LSR $B1 ;compute new speed correction value
A9 93 LDA #$93
38 SEC
E5 B1 SBC $B1
65 B0 ADC $B0
0A ASL
AA TAX
20 E2 F8 JSR $F8E2 ;schedule timer
E6 9C INC $9C ;indicate byte available
A5 B4 LDA $B4 ;if not yet estabilished
D0 11 BNE $FA44
A5 96 LDA $96 ;but sync detected
F0 26 BEQ $FA5D
85 A8 STA $A8 ;set error bits
A9 00 LDA #$00
85 96 STA $96 ;clear sync detected
A9 81 LDA #$81 ;set TA1 bit
8D 0D DC STA $DC0D ;in ICR1
85 B4 STA $B4 ;set sync estabilished
BFA44 A5 96 LDA $96 ;move sync status
85 B5 STA $B5 ;to saved sync status
F0 09 BEQ $FA53
A9 00 LDA #$00 ;if not detected,
85 B4 STA $B4 ;indicate sync not estabilished
A9 01 LDA #$01
8D 0D DC STA $DC0D
BFA53 A5 BF LDA $BF ;clear TA mask in ICR1
85 BD STA $BD ;save byte read
A5 A8 LDA $A8
05 A9 ORA $A9 ;accumulate possible errors
85 B6 STA $B6
BFA5D 4C BC FE JMP $FEBC ;exit from IRQ
;---------------------------------
;
;schedule CIA1 Timer A depending in parameter in X
;
SF8E2 86 B1 STX $B1 ;save entry parameter
A5 B0 LDA $B0 ;get speed correction
0A ASL ;* 2
0A ASL ;* 4
18 CLC
65 B0 ADC $B0 ;add speed correction
18 CLC
65 B1 ADC $B1 ;and parameter
85 B1 STA $B1 ;save low order
A9 00 LDA #$00
24 B0 BIT $B0 ;if speed correction is positive
30 01 BMI $F8F7
2A ROL ;set high oreder in A
BF8F7 06 B1 ASL $B1 ;* 2
2A ROL
06 B1 ASL $B1 ;* 4
2A ROL
AA TAX
BF8FE AD 06 DC LDA $DC06 ;wait until no change of
C9 16 CMP #$16 ;TBL1 changing
90 F9 BCC $F8FE ;while it still must be read
65 B1 ADC $B1 :add low order offset to TBL1
8D 04 DC STA $DC04 ;and store in TAL1
8A TXA
6D 07 DC ADC $DC07 ;add high order offset to TBH1
8D 05 DC STA $DC05 ;and store in TAH1
AD A2 02 LDA $02A2
8D 0E DC STA $DC0E ;set CRA1 from CRB1 activity register
8D A4 02 STA $02A4 ;and save it
AD 0D DC LDA $DC0D
29 10 AND #$10
F0 09 BEQ $F92A ;if Flag bit is not set
A9 F9 LDA #$F9 ;set exit address on stack
48 PHA
A9 2A LDA #$2A
48 PHA
4C 43 FF JMP $FF43 ;and simulate an IRQ
BF92A 58 CLI ;else allow IRQ and exit
60 RTS