; copyright HR .nolist ; supported devices ;.include "can128def.inc" ; AT90CAN128 ;.include "can32def.inc" ; AT90CAN32 ;.include "can64def.inc" ; AT90CAN64 ;.include "m1280def.inc" ; ATmega1280 ;.include "m1281def.inc" ; ATmega1281 ;.include "m1284Pdef.inc" ; ATmega1284P ;.include "m128def.inc" ; ATmega128 ;.include "m161def.inc" ; ATmega161 ;.include "m162def.inc" ; ATmega162 ;.include "m163def.inc" ; ATmega163 ;.include "m164Pdef.inc" ; ATmega164P ;.include "m165def.inc" ; ATmega165 ;.include "m165Pdef.inc" ; ATmega165P ;.include "m168def.inc" ; ATmega168 ;.include "m168Pdef.inc" ; ATmega168P ;.include "m169def.inc" ; ATmega169 ;.include "m169Pdef.inc" ; ATmega169P ;.include "m16def.inc" ; ATmega16 ;.include "m16HVAdef.inc" ; ATmega16HVA ;.include "m2560def.inc" ; ATmega2560 ;.include "m2561def.inc" ; ATmega2561 ;.include "m323def.inc" ; ATmega323 ;.include "m324Pdef.inc" ; ATmega324P ;.include "m3250def.inc" ; ATmega3250 ;.include "m3250Pdef.inc" ; ATmega3250P ;.include "m325def.inc" ; ATmega325 ;.include "m325Pdef.inc" ; ATmega325P ;.include "m328Pdef.inc" ; ATmega328P ;.include "m3290def.inc" ; ATmega3290 ;.include "m3290Pdef.inc" ; ATmega3290P ;.include "m329def.inc" ; ATmega329 ;.include "m329Pdef.inc" ; ATmega329P ;.include "m32C1def.inc" ; ATmega32C1 ;.include "m32def.inc" ; ATmega32 ;.include "m32HVBdef.inc" ; ATmega32HVB ;.include "m32M1def.inc" ; ATmega32M1 ;.include "m32U4def.inc" ; ATmega32U4 ;.include "m406def.inc" ; ATmega406 ;.include "m48def.inc" ; ATmega48 ;.include "m48Pdef.inc" ; ATmega48P ;.include "m640def.inc" ; ATmega640 ;.include "m644def.inc" ; ATmega644 ;.include "m644Pdef.inc" ; ATmega644P ;.include "m6450def.inc" ; ATmega6450 ;.include "m645def.inc" ; ATmega645 ;.include "m6490def.inc" ; ATmega6490 ;.include "m649def.inc" ; ATmega649 ;.include "m64def.inc" ; ATmega64 ;.include "m8515def.inc" ; ATmega8515 ;.include "m8535def.inc" ; ATmega8535 ;.include "m88def.inc" ; ATmega88 ;.include "m88Pdef.inc" ; ATmega88P ;.include "m8def.inc" ; ATmega8 ;.include "pwm216def.inc" ; AT90PWM216 ;.include "pwm2Bdef.inc" ; AT90PWM2B ;.include "pwm2def.inc" ; AT90PWM2 ;.include "pwm316def.inc" ; AT90PWM316 ;.include "pwm324def.inc" ; AT90PWM324 ;.include "pwm3Bdef.inc" ; AT90PWM3B ;.include "pwm3def.inc" ; AT90PWM3 ;.include "tn13def.inc" ; ATtiny13 ;.include "tn167def.inc" ; ATtiny167 ;.include "tn2313def.inc" ; ATtiny2313 ;.include "tn24def.inc" ; ATtiny24 ;.include "tn25def.inc" ; ATtiny25 ;.include "tn261def.inc" ; ATtiny261 ;.include "tn43Udef.inc" ; ATtiny43U ;.include "tn44def.inc" ; ATtiny44 ;.include "tn45def.inc" ; ATtiny45 ;.include "tn461def.inc" ; ATtiny461 ;.include "tn48def.inc" ; ATtiny48 ;.include "tn84def.inc" ; ATtiny84 .include "tn85def.inc" ; ATtiny85 ;.include "tn861def.inc" ; ATtiny861 ;.include "tn88def.inc" ; ATtiny88 ;.include "usb1286def.inc" ; AT90USB1286 ;.include "usb1287def.inc" ; AT90USB1287 ;.include "usb162def.inc" ; AT90USB162 ;.include "usb646def.inc" ; AT90USB646 ;.include "usb647def.inc" ; AT90USB647 ;.include "usb82def.inc" ; AT90USB82 ; unsupported devices ;.include "1200def.inc" ; AT90S1200 ;.include "2313def.inc" ; AT90S2313 ;.include "2323def.inc" ; AT90S2323 ;.include "2343def.inc" ; AT90S2343 ;.include "4414def.inc" ; AT90S4414 ;.include "4433def.inc" ; AT90S4433 ;.include "4434def.inc" ; AT90S4434 ;.include "8515def.inc" ; AT90S8515 ;.include "8535def.inc" ; AT90S8535 ;.include "AT86RF401def.inc" ; AT86RF401 ;.include "ATxmega128A1def.inc" ; ATxmega128A1 ;.include "ATxmega64A1def.inc" ; ATxmega64A1 ;.include "m103def.inc" ; ATmega103 ;.include "tn11def.inc" ; ATtiny11 ;.include "tn12def.inc" ; ATtiny12 ;.include "tn15def.inc" ; ATtiny15 ;.include "tn22def.inc" ; ATtiny22 ;.include "tn26def.inc" ; ATtiny26 ;.include "tn28def.inc" ; ATtiny28 #message "compile for" __PART_NAME__ .ifndef PageSize .error "Device don't support Bootloader !" .else .equ UseWDR = 1 ; set to 0/1 to de/activate WatchDog .equ UseAutobaud = 1 ; set to 0/1 to de/activate Baudrate Detection .equ UseVerify = 1 ; set to 0/1 to de/activate Verify FLASH Command, FLASH write/erase includes implicit verify, can be deactivated .equ UseE2Write = 1 ; set to 0/1 to de/activate EEPROM Write Command .equ UseE2Read = 1 ; set to 0/1 to de/activate EEPROM Read Command .equ UseSRAM = 1 ; set to 0/1 to de/activate SRAM Commands .equ UseCrypt = 0 ; set to 0/1 to de/activate cryptography .equ UseCryptFLASH = 0 ; set to 0/1 to de/activate only use cryptography for writing to FLASH .equ UseCryptE2 = 0 ; set to 0/1 to de/activate only use cryptography for writing to EEPROM .equ UartInvert = 1 ; set to 1 to invert UART levels, for RS232 drivers such as MAX232 .equ RX_PORT = PORTB ; Receive Port and Pin .equ RX = PB2 .equ TX_PORT = PORTB ; Transmit Port and Pin .equ TX = PB1 .set XTAL = 8000000 ; only important for BootDelay if Autobaud is used .set BootDelay = XTAL / 3 ; 333ms .set BootBaudrate = 9600 ; only used if no Baudrate Detection activated, XTAL is important .set BootVersion = 2 ; Version 2 .set BootCodeSize = 518 ; compile and set to value in [.cseg] Used, compile again ;.equ RWWSRE = 4 ; activate for ATmega162 in ATmega161 compatibility mode .if UseCrypt && (UseSRAM || !UseCryptFLASH || !UseCryptE2) .message "WARNING: actual settings compromise security !" .endif .include "AVRootloader.inc" ;.listmac .list .org BootStart init: cli clr zerol .if Use1Wire sbi RX_PORT, RX xout RX_DDR, zerol .else xout RX_DDR, zerol .if RX_PORT != TX_PORT xout TX_DDR, zerol .endif .if UartInvert sbi TX_PORT, TX .else cbi TX_PORT, TX .endif sbi TX_DDR, TX .endif .if UseWDR xwdr xin cmdl, WDTCR mov cmdh, cmdl ori cmdl, (1 << WDCE) ori cmdh, (1 << WDP2) | (1 << WDP1) | (1 << WDP0) xout WDTCR, cmdl xout WDTCR, cmdh .endif ldi cmdl, byte1(RamEnd) xout SPL, cmdl .ifdef SPH ldi cmdl, byte2(RamEnd) xout SPH, cmdl .endif clr zeroh ldi polyl, byte1(POLYNOM) ldi polyh, byte2(POLYNOM) .if XMega ldi cmdl, byte3(BootSign *2) xout RAMPZ, cmdl .elif RAMPZ xout RAMPZ, zerol .endif .if UseAutobaud ; baudrate detection abd: ldi cmdl, byte3(BootDelay / 6) ; autobaud based on P.Dannegger with some optimization movw xl, zerol ; detects 0x0A,0x0B,0x0D,0x0F,0x85,0x87,0xC3,0xE1 movw yl, zerol ; scan for 1x baudrate low to high followed with 4x baudrate low ab1: add yl, yl adc yh, yh movw zl, zerol ab2: sbiw xl, 1 sbc cmdl, zerol rx_0 brne ab2 breq exit ab3: sbiw yl, 1 adiw zl, 2 brcs exit rx_1 rjmp ab3 ; 8 asr yl sbiw yl, BaudTolerance adiw yl, BaudTolerance * 2 movw yl, zl brcc ab1 sbiw zl, UartLoop brcs ab1 movw baudl, zl ; identifier scanning ldi parah, (BootInfo - BootSign) * 2 ldi zl, byte1(BootSign * 2) ldi zh, byte2(BootSign * 2) ab4: rcall getc xlpm r0, z+ cp r0, paral brne abd dec parah brne ab4 .else ; fixed baudrate, identifier scanning with timeout ldi cmdl, byte1(Baudrate) ldi cmdh, byte2(Baudrate) movw baudl, cmdl abd: movw xl, zerol ldi cmdl, byte3(BootDelay / 6) ab1: ldi parah, (BootInfo - BootSign) * 2 ldi zl, byte1(BootSign * 2) ldi zh, byte2(BootSign * 2) ab2: rx_1 rjmp ab2 ab3: sbiw xl, 1 sbc cmdl, zerol rx_0 brne ab3 breq exit rcall getx xlpm r0, z+ cp r0, paral brne abd dec parah brne ab2 .endif ; send info about chip/bootloader info: ldi parah, (BootEnd - BootInfo) * 2 inf1: xlpm paral, z+ rcall putc dec parah brne inf1 ; main commandloop ; 0=run/restart ; 1=program flash, 2=erase flash, 3=verify flash, 4=e2read, 5=e2write, 6=read SRAM ; 0xFF=set address, 0xFD=set buffer and decryption main: ldi paral, SUCCESS mai1: rcall putc movw crcl, zerol rcall getw movw cmdl, paral cpi cmdh, 0xFE brlo mai2 rcall getw movw yl, paral sbrc cmdh, 0 movw zl, paral .if XMega sbrc cmdh, 0 mov zx, cmdl .endif mai2: rcall getw mai3: ldi paral, ERRORCRC brne mai1 cpi cmdh, 0xFE breq setbuf brsh main cpse cmdh, zerol rjmp mai4 ; run/restart cpse cmdl, zerol exit: jmpapp rjmp init ; set buffer setbuf: .if UseCrypt clr flag cpi cmdl, 0xFE brlo set3 bst cmdl, 0 ldi cnt, 8 ; encrypted data follows, copy last feedback to sraml-16 ldi zl, byte1(SRAM_START) ldi zh, byte2(SRAM_START) movw xl, feedl adiw xl, 8 ; last feedback clr r0 set1: brtc set2 ld r0, x+ set2: st z+, r0 dec cnt brne set1 .endif set3: ldi xl, byte1(SRAM_START + 16) ldi xh, byte2(SRAM_START + 16) movw sraml, xl .if UseSRAM cpi cmdl, 1 brne set4 movw xl, zl .endif set4: rcall getc st x+, paral sbiw yl, 1 brne set4 .if !UseCrypt inc cmdh rjmp mai2 .else rcall getw brne mai3 ori flag, 0x01 set5: cpi cmdl, 0xFE brlo main rjmp decrypt .endif mai4: cpi cmdh, 3 brsh mai5 ; program/erase bst cmdh, 0 prog: ldi paral, ERRORPROG .if UseCrypt brtc pro1 .if UseCryptFLASH cpi flag, 3 .else cpi flag, 1 .endif brlo pro8 .endif pro1: movw yl, sraml pro2: cpi zl, byte1(BootStart *2) ldi parah, byte2(BootStart *2) cpc zh, parah .if XMega xout RAMPZ, zx ldi parah, byte3(BootStart *2) cpc zx, parah .endif brsh pro8 ldi parah, (1 << PGERS) | (1 << SPMEN) ; erase page .if BLS rcall flash .else xout SPMCSR, parah spm .endif brtc pro5 ldi cnt, pagesize pro4: ld r0, y+ ld r1, y+ ldi parah, (1 << SPMEN) .if BLS rcall flash .else xout SPMCSR, parah spm .endif adiw zl, 2 dec cnt brne pro4 subi zl, byte1(PageSize *2) sbci zh, byte2(PageSize *2) subi yl, byte1(PageSize *2) sbci yh, byte2(PageSize *2) ldi parah, (1 << PGWRT) | (1 << SPMEN) ; program page .if !BLS xout SPMCSR, parah spm .else rcall flash .endif pro5: .if BLS .ifdef RWWSRE ldi parah, (1 << RWWSRE) | (1 << SPMEN) ; unlock section rcall flash .endif .endif .if PageSize * 2 > 256 .error "PageSize is greater as 256 bytes, check programing loops" .endif ldi cnt, byte1(pagesize * 2) ldi paral, ERRORVERIFY ldi parah, 0xFF pro6: xlpm r0, z+ brtc pro7 ld parah, y+ pro7: cpse parah, r0 pro8: rjmp mai1 dec cnt brne pro6 .if XMega cp zl, zerol cpc zh, zeroh brne pro9 inc zx .endif pro9: brtc proA cp yl, xl cpc yh, xh brlo pro2 brne pro8 proA: dec cmdl brne prog rjmp main mai5: .if !UseVerify && (UseE2Write || UseE2Read || UseSRAM) breq mai8 .elif UseVerify brne mai6 veri: ldi paral, ERRORVERIFY ver1: movw yl, sraml ver2: ld r0, y+ .if XMega xout RAMPZ, zx xlpm r1, z adiw z, 1 adc zx, zerol .else xlpm r1, z+ .endif cpse r0, r1 rjmp mai1 cp yl, xl cpc yh, xh brne ver2 dec cmdl brne ver1 rjmp main .endif mai6: .if UseE2Read && UseE2Write cpi cmdh, 6 brsh mai7 bst cmdh, 0 .if UseCrypt brtc ee1 ldi paral, ERRORPROG .if UseCryptE2 cpi flag, 3 .else cpi flag, 1 .endif brlo maiZ .endif ee1: ldi paral, ERRORVERIFY movw yl, sraml ee2: xout EEARL, zl .ifdef EEARH xout EEARH, zh .endif adiw zl, 1 brts ee3 sbi EECR, EERE in paral, EEDR rcall putp rjmp ee5 ee3: ld r0, y+ xout EEDR, r0 sbi EECR, EEMWE sbi EECR, EEWE ee4: sbic EECR, EEWE rjmp ee4 sbi EECR, EERE in r1, EEDR cpse r1, r0 rjmp maiZ cp yl, xl cpc yh, xh brne ee2 ee5: dec cmdl brne ee1 brts ee6 rcall putw ee6: rjmp main .elif UseE2Read cpi cmdh, 5 breq maiY brsh mai7 ee1: xout EEARL, zl .ifdef EEARH xout EEARH, zh .endif adiw zl, 1 sbi EECR, EERE in paral, EEDR rcall putp dec cmdl brne ee1 rcall putw rjmp main .elif UseE2Write cpi cmdh, 5 brlo maiY brne mai7 .if UseCrypt ldi paral, ERRORPROG .if UseCryptE2 cpi flag, 3 .else cpi flag, 1 .endif brlo maiZ .endif ee1: ldi paral, ERRORVERIFY movw yl, sraml ee2: xout EEARL, zl .ifdef EEARH xout EEARH, zh .endif adiw zl, 1 ld r0, y+ xout EEDR, r0 sbi EECR, EEMWE sbi EECR, EEWE ee3: sbic EECR, EEWE rjmp ee3 sbi EECR, EERE in r1, EEDR cpse r1, r0 rjmp maiZ cp yl, xl cpc yh, xh brne ee2 dec cmdl brne ee1 rjmp main .endif mai7: .if UseSRAM .if !UseE2Read || !UseE2Write cpi cmdh, 6 .endif brne mai8 sram: ld paral, z+ rcall putp dec cmdl brne sram rcall putw rjmp main .endif mai8: ; here new command #7 maiY: ldi paral, ERRORCOMMAND maiZ: rjmp mai1 ; SPM helper .if BLS flash: xout SPMCSR, parah spm flas2: xin parah, SPMCSR sbrc parah, SPMEN rjmp flas2 ret .endif ; send char with crc .if UseE2Read || UseSRAM putw: movw paral, crcl rcall putc mov paral, parah putp: eor crcl, paral ldi cnt, 8 put1: lsr crch ror crcl brcc put2 eor crch, polyh eor crcl, polyl put2: dec cnt brne put1 .endif ; send char putc: rcall waitf rcall waitf ldi cnt, 10 com paral put3: tx_out ; 1 rcall waitf ; 15 lsr paral ; 1 brcs put4 ; 1 tx_0 ; 1 put4: rcall put5 ; 7 dec cnt ; 1 brne put3 ; 2 = 29 + baud put5: ret ; receive char/word getw: rcall getc getm: mov parah, paral getc: rx_1 rjmp getc get1: xwdr rx_0 rjmp get1 getx: ldi cnt, 8 movw cntl, baudl lsr cnth ror cntl rcall waith get2: rcall waitf ; 15 + baud lsr paral ; 1 rx_0 ; 1 ori paral, 0x80 ; 1 sbrc paral, 7 ; 1 eor crcl, polyl ; 1 lsr crch ; 1 ror crcl ; 1 brcc get3 ; 1 eor crch, polyh ; 1 get3: brcc get4 ; 1 eor crcl, polyl ; 1 get4: dec cnt ; 1 brne get2 ; 2 = 29 + baud eor crcl, crch eor crcl, crch ret ; UART delays ; by P.Dannegger waitf: movw cntl, baudl ; 1 waith: sbiw cntl, 4 ; 2 brcc waith ; 2/1 cpi cntl, 0xFD ; 1 brcs wait1 ; 2/1 (2) breq wait1 ; 2/1 (3) cpi cntl, 0xFF ; 1 breq wait1 ; 2/1 (4/5) wait1: tx_1 ; 1 ret ; 4 + 3 (rcall) = 15 .if UseCrypt .nolist .undef feedl .undef feedh .undef crcl .undef crch .undef paral .undef parah .undef polyl .undef polyh .undef zx .undef cmdl .undef cmdh .undef cntl .undef cnth .def a0 = r0 .def a1 = r1 .def a2 = r2 .def a3 = r3 .def b0 = r4 ; feedl .def b1 = r5 ; feedh .def b2 = r6 .def b3 = r7 ; zx .def t0 = r8 ; crcl .def t1 = r9 ; crch ;.def zerol = r10 ; zerol ;.def zeroh = r11 ; zeroh ;.def baudl = r12 ; baudl ;.def baudh = r13 ; baudl ;.def sraml = r14 ; SRAM buffer start ;.def sramh = r15 .def s0 = r16 ; paral .def s1 = r17 ; parah .def s2 = r18 ; polyl, restored .def s3 = r19 ; polyh, restored ;.def cnt = r20 ; cnt ;.def flag = r21 ; flag .def round = r22 ; cmdl .def t4 = r23 ; cmdh .def t2 = r24 ; cntl .def t3 = r25 ; cnth ;.def XL = r26 ; buffer_endl ;.def XH = r27 ; buffer_endh ;.def YL = r28 ; unused ;.def YH = r29 ; unused ;.def ZL = r30 ; overwritten Address ;.def ZH = r31 ; .list ; XTEA decryption with spezial CBC feedback modus, final checksum/signature block with 24 Bit FLASH Address setup ; SRAML points to start of buffer, SRAML-16 points to feedback, SRAML-8 points to buffer of next used feedback ; X points after buffer ; T flag = 0 = CMDL = 0xFE = decryption init, = 1 = CMDL = 0xFF = normal data decryption ; cycles: ; 698 * 8 Bytes -1 = 5583 one data block ; + 98 for CMDL=0xFE=init ; + 76 for CMDL=0xFF=data ; thus average throughput by 8Mhz is 11kByte/sec decrypt: movw yl, sraml sbiw yl, 16 movw sraml, yl sbiw xl, 16 .if XMega ldi cnt, byte3(BootKey * 2) xout RAMPZ, cnt .endif tea1: ldi cnt, 8 movw zl, zerol ; zerol, z points to register file r0-r7 = a,b tea2: ldd t0, y +0 ; buffer = feedback xor cipherblock ldd t1, y +16 ; in next round buffer is feedback eor t0, t1 std y +8, t0 st z+, t1 ; setup a,b = cipherblock adiw yl, 1 dec cnt brne tea2 ldi s0, 0x20 ; sum = 32 * delta ldi s1, 0x37 ldi s2, 0xef ldi s3, 0xc6 rcall teadec ; first 16 rounds tea3: ld t0, z ; ciphertext = ciphertext xor feedback ld t1, y+ eor t0, t1 st z+, t0 dec cnt brne tea3 rcall teadec ; second 16 rounds tea4: ld t0, z+ ; plaintext = ciphertext xor feedback ld t1, y eor t0, t1 st y+, t0 dec cnt brne tea4 cp yl, xl ; sram buffer end reached ? cpc yh, xh brlo tea1 ldi s0, ERRORCRYPT brcs tea7 ; overflow, datasize not a multiple of 8 ori flag, 0x02 ; success crypt ldi zl, byte1(BootKey * 2) ldi zh, byte2(BootKey * 2) sbiw yl, 8 ; points to signature + dummy + address movw xl, yl ldi cnt, 4 ; check signature brts tea5 sbiw zl, 4 ldi cnt, 8 ; check BootInfo and signature tea5: ld r0, y+ xlpm r1, z+ cpse r0, r1 clr flag ; signature don't match dec cnt brne tea5 movw b0, xl ; feedback brtc tea6 ldd s1, y +0 ; correct XH:XL = bufferend pointer for non-multiple-of-8 data sub xl, s1 sbc xh, zerol ldd b3, y +1 ; ZX:ZH:ZL, new FLASH/EEPROM Address, only valid if cmdl = 0xFF ldd zh, y +2 ldd zl, y +3 tea6: sbrc flag, 1 ldi s0, SUCCESS tea7: ldi s2, byte1(POLYNOM) ; restore CRC polynom ldi s3, byte2(POLYNOM) rjmp mai1 ; plain XTEA decryption teadec: ldi round, 32 ; 16 round XTEA, r0-r7=a,b ciphertext movw t0, a0 movw t2, a2 dec1: clr t4 ; t = (a shr 4) xor (a shl 5) ldi cnt, 3 dec2: lsl t0 rol t1 rol t2 rol t3 rol t4 dec cnt brne dec2 lsl t0 eor t0, t1 rol t1 eor t1, t2 rol t2 eor t2, t3 rol t3 eor t3, t4 add t0, a0 ; t = ((a shr 4) xor (a shl 5)) + a adc t1, a1 adc t2, a2 adc t3, a3 ldi zl, low(BootKey *2) ldi zh, high(BootKey *2) sbrc round, 0 rjmp dec3 sbrc s1, 3 ; k = key[(sum shr 11) and 3] adiw zl, 4 sbrc s1, 4 adiw zl, 8 rjmp dec4 dec3: subi s0, 0xB9 ; sum = sum - delta sbci s1, 0x79 sbci s2, 0x37 sbci s3, 0x9E sbrc s0, 0 ; k = key[sum and 3] adiw zl, 4 sbrc s0, 1 adiw zl, 8 dec4: xlpm t4, z+ ; t = t xor (sum + k) add t4, s0 eor t0, t4 xlpm t4, z+ adc t4, s1 eor t1, t4 xlpm t4, z+ adc t4, s2 eor t2, t4 xlpm t4, z+ adc t4, s3 eor t3, t4 sub b0, t0 ; b = b - t sbc b1, t1 sbc b2, t2 sbc b3, t3 movw t0, b0 ; b = a, a = b movw t2, b2 movw b0, a0 movw b2, a2 movw a0, t0 movw a2, t2 dec round brne dec1 ldi cnt, 8 movw zl, zerol sbiw yl, 8 ret .endif BootSign: .db "BOOT" ; iff you change it then change Sign in AVRootloader.ini BootInfo: .db SIGNATURE_001, SIGNATURE_002, BootVersion, BootPages BootEnd: .if UseCrypt ; follow bytes should be keept secret and choosen randomly, 128 Bit Password, first 32 bit used as Signature BootKey: .db $14,$70,$A1,$B3,$05,$73,$D7,$DC,$E9,$2F,$50,$6A,$64,$CD,$6B,$BA .endif .endif