-----------Math Blaster Plus----------- A 4am crack 2015-11-03 --------------------------------------- Name: Math Blaster Plus Version: 3.7 Genre: educational Year: 1987 Credits: Designed by Jan Davidson and Cathy Johnson Written in Forth by Louis X. Savain Publisher: Davidson & Associates, Inc. Media: 3.5-inch floppy (800K) OS: ProDOS 1.4 Previous cracks: Asimov has an uncredited crack Similar cracks: #318 Math Blaster Plus 1.6 (5.25") #300 Read 'N Roll (5.25") ~ Chapter 0 In Which Various Automated Tools Fail In Interesting Ways Copy ][+ 9.1 ("COPY" > "DISK") read error on block $0308; copy loads ProDOS then quits to program selector CFFA 3000 import read error on block 776 (= $0308); booting the disk image in an emulator exhibits the same behavior as the backup I made with Copy ][+ disk copy (boots ProDOS then quits via a "Quit" MLI call) It's a very ProDOS-y way of saying "f--- you." The original disk boots without complaint, so either this bad block is part of the protection check, or it's unrelated and I got incredibly lucky. Next steps: 1. Trace the first .SYSTEM file 2. Find and disable the protection check 3. Declare victory(*) (*) Take a nap ~ Chapter 1 In Which We Learn Many Things, But Not The Right Things [S7,D1=my ProDOS hard drive] [S5,D1=original disk] ]PR#7 ... ]CAT,S5,D1 /MATH.0 NAME TYPE BLOCKS MODIFIED PICTURE0 TXT 9 MATH TXT 31 9-MAY-88 GAME TXT 30 26-APR-88 MBPTEXT TXT 5 15-OCT-87 GOODIES TXT 9 TK.DATA TXT 14 11-NOV-87 SPRITES1 TXT 4 SPRITES2 TXT 14 MATH.SYSTEM SYS 3 6-OCT-87 QUIT BIN 1 6-OCT-87 VIEW TXT 10 12-NOV-87 CUST TXT 7 5-JAN-88 PACK TXT 3 11-NOV-87 NAME TXT 4 12-NOV-87 SPRITES1.1 TXT 4 PRODOS SYS 32 TK.ABS BIN 36 TEST.FONT BIN 7 GETPIC TXT 3 11-NOV-87 EMENU TXT 5 TK.DATA1 TXT 1 EDTEXT TXT 3 19-OCT-87 ENTRY.PIC TXT 7 MBP.CERT1 TXT 9 MATH.BLASTER BIN 85 12-NOV-87 CERT TXT 10 11-NOV-87 PRINTER.DRIVERS TXT 12 PRINTER.DATA TXT 1 1-JAN-87 INTER.DRIVERS TXT 5 EDIT TXT 31 9-MAY-88 DUMP.HIRES.R BIN 5 CTEXT TXT 1 2-OCT-87 ENTR TXT 3 DATA DIR 3 7-MAR-62 FINDER.DATA $C9 3 7-MAR-62 BLOCKS FREE: 1075 BLOCKS USED: 525 ]PREFIX /MATH.0 ]-MATH.SYSTEM ...works... OK, I can boot from my hard drive, then run the program successfully from the original disk. Whatever copy protection there is, it's not dependent on the PRODOS file. [S5,D1=non-working copy] ]PR#7 ]PREFIX /MATH.0 ]-MATH.SYSTEM ...quits via ProDOS quit handler... Time to start tracing MATH.SYSTEM. ]PR#7 ]PREFIX /MATH.0 ]BLOAD MATH.SYSTEM,A$2000,TSYS ]CALL -151 *2000L 2000- A2 00 LDX #$00 2002- BD 1A 20 LDA $201A,X 2005- 9D 01 08 STA $0801,X 2008- BD 1A 21 LDA $211A,X 200B- 9D 01 09 STA $0901,X 200E- BD 1A 22 LDA $221A,X 2011- 9D 01 0A STA $0A01,X 2014- E8 INX 2015- D0 EB BNE $2002 2017- 4C 01 08 JMP $0801 *2017:60 *2000G *801L 0801- 4C 52 08 JMP $0852 *852L . . ProDOS-y initialization . ; subroutine gets file info via ProDOS ; MLI (command $C4) -- filename is at ; the address pointed to by (X/A) 087D- A9 5B LDA #$5B 087F- A2 0A LDX #$0A 0881- 20 C4 09 JSR $09C4 The string at $0A5B is "MATH.BLASTER". ; open the "MATH.BLASTER" file (MLI ; command $C8), but at $4060 instead of ; its default starting address ($0860, ; according to an extended catalog ; listing) 0884- A9 60 LDA #$60 0886- 8D 8F 0A STA $0A8F 0889- A9 40 LDA #$40 088B- 8D 90 0A STA $0A90 088E- 20 D9 09 JSR $09D9 ; get file EOF (MLI $D1) 0891- 20 01 0A JSR $0A01 ; read part of the file (MLI $CA) 0894- A9 A0 LDA #$A0 0896- 85 64 STA $64 0898- A9 17 LDA #$17 089A- 85 65 STA $65 089C- AD 8C 0A LDA $0A8C 089F- 8D 29 09 STA $0929 08A2- AD 8D 0A LDA $0A8D 08A5- 8D 2A 09 STA $092A 08A8- 20 1F 0A JSR $0A1F 08AB- 20 4E 09 JSR $094E *94EL ; copy to aux memory 094E- A9 60 LDA #$60 0950- 85 3C STA $3C 0952- 85 42 STA $42 0954- A9 40 LDA #$40 0956- 85 3D STA $3D 0958- A9 08 LDA #$08 095A- 85 43 STA $43 095C- A9 00 LDA #$00 095E- 85 3E STA $3E 0960- A9 58 LDA #$58 0962- 85 3F STA $3F 0964- 38 SEC 0965- 4C 11 C3 JMP $C311 According to "Inside the Apple //e" (pp. 296-8), $C311 copies data from main memory to aux memory and back. (Aux memory is what you get by having an 80-column card, 128K instead of 64.) The routine itself takes 4 parameters: ($3C/$3D) starting address ($3E/$3F) ending address ($42/$43) destination address in the other memory bank carry bit set for main->aux copy, or clear for aux->main copy So this is copying $4060..$5800 in main memory (from the "MATH.BLASTER" file we just read) to $0860 in aux memory. *8AEL ; set the mark within the open ; "MATH.BLASTER" file (MLI $CE) 08AE- 20 2B 09 JSR $092B ; read more of the file (MLI $CA) 08B1- A9 00 LDA #$00 08B3- 85 60 STA $60 08B5- 85 64 STA $64 08B7- A9 40 LDA #$40 08B9- 85 61 STA $61 08BB- A9 50 LDA #$50 08BD- 85 65 STA $65 08BF- 20 1F 0A JSR $0A1F ; and copy that part to aux memory 08C2- A9 00 LDA #$00 08C4- 85 3C STA $3C 08C6- 85 42 STA $42 08C8- 85 3E STA $3E 08CA- A9 40 LDA #$40 08CC- 85 3D STA $3D 08CE- 85 43 STA $43 08D0- A9 90 LDA #$90 08D2- 85 3F STA $3F 08D4- 38 SEC 08D5- 20 11 C3 JSR $C311 ; check if we're at the end of the file ; yet (I assume because this is a ; generic routine) -- on this disk it ; always returns #$FF 08D8- 20 13 09 JSR $0913 ; will not branch 08DB- F0 2D BEQ $090A ; read more from the file 08DD- A9 20 LDA #$20 08DF- 85 65 STA $65 08E1- A9 00 LDA #$00 08E3- 85 60 STA $60 08E5- A9 FF LDA #$FF 08E7- 85 64 STA $64 08E9- A9 40 LDA #$40 08EB- 85 61 STA $61 08ED- 20 1F 0A JSR $0A1F ; and copy it to aux memory 08F0- A9 00 LDA #$00 08F2- 85 3C STA $3C 08F4- 85 42 STA $42 08F6- A9 FF LDA #$FF 08F8- 85 3E STA $3E 08FA- A9 40 LDA #$40 08FC- 85 3D STA $3D 08FE- A9 90 LDA #$90 0900- 85 43 STA $43 0902- A9 60 LDA #$60 0904- 85 3F STA $3F 0906- 38 SEC 0907- 20 11 C3 JSR $C311 ; close the file (MLI $CC) 090A- 20 47 0A JSR $0A47 ; copy more bits to aux memory 090D- 20 68 09 JSR $0968 0910- 4C 80 09 JMP $0980 *980L ; copy even more bits to aux memory 0980- A9 00 LDA #$00 0982- 85 3C STA $3C 0984- 85 42 STA $42 0986- A9 08 LDA #$08 0988- 85 3D STA $3D 098A- 85 43 STA $43 098C- A9 5F LDA #$5F 098E- 85 3E STA $3E 0990- A9 08 LDA #$08 0992- 85 3F STA $3F 0994- 38 SEC 0995- 20 11 C3 JSR $C311 ; subroutine to read an entire file -- ; filename is at the address pointed to ; by (X/A), $0A68, which is "TK.ABS" 0998- A9 68 LDA #$68 099A- A2 0A LDX #$0A 099C- 20 B5 09 JSR $09B5 ; read file "TEST.FONT" 099F- A9 6F LDA #$6F 09A1- A2 0A LDX #$0A 09A3- 20 B5 09 JSR $09B5 ; set up a jump to AUX memory (see ; "Inside the Apple //e", p. 300) ; jump address ($0860) is stored in ; $03ED/$03EE 09A6- A9 60 LDA #$60 09A8- 8D ED 03 STA $03ED 09AB- A9 08 LDA #$08 09AD- 8D EE 03 STA $03EE ; use main memory's stack and zero page 09B0- B8 CLV ; transfer control from main to aux mem 09B1- 38 SEC ; call XFER routine 09B2- 4C 14 C3 JMP $C314 *9B2:4C 59 FF *801G Execution reaches here, even on my non- working copy. I haven't found the copy protection routine yet. ~ Chapter 2 FORTH? In *My* Apple II? It's More Likely Than You Think Execution continues in auxiliary memory at $0860 (set up at $09A6). This code was loaded from the "MATH.BLASTER" file at $4060 then moved to aux memory. Patching it may be tricky (if it comes to that), but I can load it up in main memory and take a look. ]BLOAD MATH.BLASTER,A$860 NO BUFFERS AVAILABLE Ah, because the file itself is too big to be loaded all at once. (It's over $C000 bytes long.) OK, let's reuse the partial loader in MATH.SYSTEM. *8AB:60 ; put RTS after first loader *87DG ; load part of file at $4060 *860<4060.57FFM ; move code into place *860L 0860- 4C BE 18 JMP $18BE *18BEL ; zero page initialization 18BE- A9 19 LDA #$19 18C0- 85 19 STA $19 18C2- A9 68 LDA #$68 18C4- 85 18 STA $18 ; check for 80-column card using bit 1 ; of the machine ID (set by ProDOS) 18C6- A9 02 LDA #$02 18C8- 2C 98 BF BIT $BF98 18CB- F0 05 BEQ $18D2 ; switch to 80-column mode 18CD- A9 00 LDA #$00 18CF- 20 00 C3 JSR $C300 ; is DELETE key held down on boot? 18D2- AD 00 C0 LDA $C000 18D5- AC 10 C0 LDY $C010 18D8- C9 FF CMP #$FF ; if yes, branch over a bunch of ; stuff (I tried this and the program ; crashed -- maybe it hooks into some ; external routines that the developers ; had installed?) 18DA- F0 6E BEQ $194A ; other initialization 18DC- A0 FF LDY #$FF 18DE- 84 32 STY $32 18E0- AD DF 0E LDA $0EDF 18E3- 8D EA 0E STA $0EEA 18E6- AD E0 0E LDA $0EE0 18E9- 8D EB 0E STA $0EEB 18EC- A9 00 LDA #$00 18EE- A2 11 LDX #$11 18F0- 9D A3 08 STA $08A3,X 18F3- CA DEX 18F4- 10 FA BPL $18F0 ; $0885 is a wrapper around the ProDOS ; MLI, and this command ($CC) closes ; all open files and flushes everything 18F6- A9 CC LDA #$CC 18F8- 8D 8A 08 STA $088A 18FB- E8 INX 18FC- 8E 92 08 STX $0892 18FF- E8 INX 1900- 8E 91 08 STX $0891 1903- 20 85 08 JSR $0885 ; set the Ctrl-Y vector 1906- A9 4C LDA #$4C 1908- 8D F8 03 STA $03F8 190B- A9 4A LDA #$4A 190D- 8D F9 03 STA $03F9 1910- A9 19 LDA #$19 1912- 8D FA 03 STA $03FA ; set reset vector 1915- A9 BE LDA #$BE 1917- 8D F2 03 STA $03F2 191A- A9 18 LDA #$18 191C- 8D F3 03 STA $03F3 191F- 20 6F FB JSR $FB6F ; clear system bitmap (tracks which ; memory pages are in use) 1922- A9 00 LDA #$00 1924- A2 17 LDX #$17 1926- 9D 58 BF STA $BF58,X 1929- CA DEX 192A- 10 FA BPL $1926 192C- AD 84 08 LDA $0884 192F- 8D FD BF STA $BFFD 1932- AD 6C 08 LDA $086C 1935- 8D 38 18 STA $1838 1938- AD 6D 08 LDA $086D 193B- 8D 39 18 STA $1839 193E- A0 17 LDY #$17 1940- D0 0A BNE $194C ;[always skipped] ;1942- A9 18 LDA #$18 ;1944- 85 19 STA $19 ;1946- A9 9B LDA #$9B ;1948- 85 18 STA $18 ;194A- A0 0F LDY #$0F ; memory move 194C- AD 70 08 LDA $0870 194F- 85 86 STA $86 1951- AD 71 08 LDA $0871 1954- 85 87 STA $87 1956- B9 6C 08 LDA $086C,Y 1959- 91 86 STA ($86),Y 195B- 88 DEY 195C- 10 F8 BPL $1956 195E- D8 CLD 195F- A9 6C LDA #$6C 1961- 85 1A STA $1A 1963- 4C 0D 0C JMP $0C0D *C0DL ; stack fiddling 0C0D- 86 88 STX $88 0C0F- A0 08 LDY #$08 0C11- B1 86 LDA ($86),Y 0C13- AA TAX 0C14- 9A TXS 0C15- A6 88 LDX $88 0C17- 4C D5 08 JMP $08D5 *8D5L ; ah, ($18) is an address, initialized ; as $1968 (at $18BE) 08D5- A0 01 LDY #$01 ; get the word at ($18) and put it in ; zero page $1B and $1C 08D7- B1 18 LDA ($18),Y 08D9- 85 1C STA $1C 08DB- 88 DEY 08DC- B1 18 LDA ($18),Y 08DE- 85 1B STA $1B ; increment ($18) by 2 08E0- 18 CLC 08E1- A5 18 LDA $18 08E3- 69 02 ADC #$02 08E5- 85 18 STA $18 08E7- 90 02 BCC $08EB 08E9- E6 19 INC $19 ; zero page $1A is $6C (set at $195F), ; so this is all an indirect jump to ; the address listed at $1968 08EB- 4C 1A 00 JMP $001A *1968.1969 1968- 1D 1B *1B1D.1B1E 1B1D- F2 0D Execution continues at $0DF2. *DF2L ; fiddling with the addresses again ; ($1B) and ($18) 0DF2- A5 19 LDA $19 0DF4- 48 PHA 0DF5- A5 18 LDA $18 0DF7- 48 PHA 0DF8- 18 CLC 0DF9- A5 1B LDA $1B 0DFB- 69 02 ADC #$02 0DFD- 85 18 STA $18 0DFF- 98 TYA 0E00- 65 1C ADC $1C 0E02- 85 19 STA $19 ; and jump back to...$08D5 0E04- 4C D5 08 JMP $08D5 Oh no. I just figured this out. This is not the code I'm looking for. This is the code that interprets the code I'm looking for. This is an interpreter. Now what? I've had some limited success with reverse engineering Pascal p-code, but that was 1. painful, 2. only possible because p-code is well known and meticulously documented and I already knew Pascal, and 3. still painful The about box says it was written in Forth. I don't know any Forth. I've never used Forth on any computer, 8-bit or otherwise. There were apparently several different versions of Forth on the Apple II, but I don't know which version this disk uses. Let's regroup. ~ Chapter 3 In Which We Regroup, Take Stock, And Continue From First Principles Here's what I know: * The program runs (from the original disk) even if I first boot ProDOS from my hard drive. That's not even the same version of ProDOS as the original disk. * Other disks by this publisher (e.g. #478 Word Attack Plus! Spanish) have similar disk characteristics (single unreadable block) and behavior (copies quit via ProDOS program selector). * Under ProDOS, "quitting via the program selector" and "reading a raw sector" are accomplished by sending commands to the ProDOS MLI. * All calls to the ProDOS MLI route through the same entry point ($BF00). This gives me an idea. Since I can run this program even after booting ProDOS from my hard drive, maybe I can also install a little logging program that lets me see ProDOS MLI calls as they whiz by. ]PR#7 ]CALL -151 *BF00L BF00- 4C 4B BF JMP $BF4B OK, at the end of my routine, I'll need to jump to $BF4B to get to the real MLI entry point. ; get, print, and store the top address ; on the stack 0100- BA TSX ; high byte 0101- BD 02 01 LDA $0102,X 0104- 8D 22 01 STA $0122 0107- 20 DA FD JSR $FDDA ; low byte 010A- BD 01 01 LDA $0101,X 010D- 8D 21 01 STA $0121 0110- 20 DA FD JSR $FDDA ; print a space 0113- A9 A0 LDA #$A0 0115- 20 F0 FD JSR $FDF0 ; increment the address we got earlier ; (and put in $0121/$0122) 0118- EE 21 01 INC $0121 011B- D0 03 BNE $0120 011D- EE 22 01 INC $0122 ; this line of code will now load the ; MLI command that was passed to the ; ProDOS MLI (it's the byte immediately ; after the JSR to the MLI entry point) 0120- AD FF FF LDA $FFFF ; print that too 0123- 20 DA FD JSR $FDDA ; print a carriage return 0126- A9 8D LDA #$8D 0128- 20 F0 FD JSR $FDF0 ; wait for a key 012B- 2C 10 C0 BIT $C010 012E- AD 00 C0 LDA $C000 0131- 10 FB BPL $012E ; jump to real MLI 0133- 4C 4B BF JMP $BF4B *PREFIX /MATH.0 *BLOAD MATH.SYSTEM,A$2000,TSYS ; route all MLI calls through my logger *BF00:4C 00 01 ; run the program *2000G I've annotated the following output with the name of each MLI command. The logger only outputs the two columns of hex (stack address + command ID). 09D1 C4 ; GET_FILE_INFO 09F4 C8 ; OPEN 0A0D D1 ; GET_EOF 0A3F CA ; READ 0946 CE ; SET_MARK 0A3F CA ; READ 0A3F CA ; READ 0A53 CC ; CLOSE 09D1 C4 ; GET_FILE_INFO 09F4 C8 ; OPEN 0A0D D1 ; GET_EOF 0A3F CA ; READ 0A53 CC ; CLOSE 09D1 C4 ; GET_FILE_INFO 09F4 C8 ; OPEN 0A0D D1 ; GET_EOF 0A3F CA ; READ 0A53 CC ; CLOSE [...clears screen in 80-column mode and continues...] 0889 CC ; CLOSE 0889 80 ; READ_BLOCK <-- ! 0889 C8 ; OPEN 0889 D1 ; GET_EOF 0889 CA ; READ 0889 CD ; FLUSH 4053 65 ; QUIT Several interesting things here. I've stepped through some of the initial commands already, so they come as no surprise. I know that it loads three files, "MATH.BLASTER", "TK.ABS", and "TEST.FONT". I even know where it switches to 80-column mode ($18CF). But that's where it gets interesting. After it switches to 80-column mode, every stack address is the same ($0889) except the final QUIT command ($4059). So all of those MLI calls are coming from inside the interpreter, presumably some MLI wrapper function. Also, that READ_BLOCK (command $80) is very suspicious. That's a raw sector read (really two sectors, since ProDOS does everything by blocks). So, it 1. issues a READ_BLOCK MLI call, then 2. opens/reads/closes a file, then 3. quits at $4053 Also, there is a file named "QUIT" on this disk which loads at $4000. If that isn't all connected, it would be a coincidence of magnificent proportions. ~ Chapter 4 Success Is Failure, Failure Is Success, Black Is White, Night Is Day, Teaching Is Dead I have another crazy idea: upgrade my logger to modify the MLI command on the fly. That is, if the command on entry is $80 (READ_BLOCK), change it to something else, like $CC (CLOSE). Why CLOSE? Because it will always fail. No, really. READ_BLOCK takes 3 parameters; CLOSE takes 1. The first thing the MLI does is check whether the parameter count is correct for the given command; if not, it exits immediately with return code $02. (And remember, I want the READ_BLOCK command to fail. On the original disk, it fails because of the intentionally bad sector on track $22. Failure is success.) ]PR#7 ]CALL -151 ; get the top stack address and put it ; in $0116/$0117 0100- BA TSX 0101- BD 02 01 LDA $0102,X 0104- 8D 17 01 STA $0117 0107- BD 01 01 LDA $0101,X 010A- 8D 16 01 STA $0116 ; increment the address we got from ; the stack (does not touch the stack ; itself, just our local copy) 010D- EE 16 01 INC $0116 0110- D0 03 BNE $0115 0112- EE 17 01 INC $0117 ; now this instruction will load the ; MLI command 0115- AD FF FF LDA $FFFF ; is it READ_BLOCK? 0118- C9 80 CMP #$80 ; no, branch without changing anything 011A- D0 11 BNE $012D ; yes, change the MLI command to CLOSE ; by modifying the STA instruction at ; $012A 011C- AD 16 01 LDA $0116 011F- 8D 2B 01 STA $012B 0122- AD 17 01 LDA $0117 0125- 8D 2C 01 STA $012C 0128- A9 CC LDA #$CC 012A- 8D FF FF STA $FFFF ; call the real MLI 012D- 4C 4B BF JMP $BF4B *PREFIX /MATH.0 *BLOAD MATH.SYSTEM,A$2000,TSYS ; set the trap *BF00:4C 00 01 ; execute the program *2000G ...works, and it is glorious... Now I have a way to get a copy to load. But how should I make this change permanent? I could install this MLI trap on boot and literally route every MLI command through it, changing any READ_BLOCK commands on the fly. But I'm not 100% sure that there aren't other legitimate uses of READ_BLOCK buried deep within the program. I'm also not 100% sure that the trap won't get overwritten if the stack gets too big, and I'm not sure where else I could put it in memory that wouldn't ever be used during the entire lifetime of the program. It all just feels inelegant. It's not a solution; it's a prototype. A real solution would be to find the interpreted code that calls that MLI function wrapper (near $889) with the READ_BLOCK command and checks for its expected failure. Then I can either change the if-then logic, or NOP it out somehow, or possibly just change that one READ_BLOCK command to CLOSE. Like my prototype, but without affecting every single MLI call and without taking up any extra memory. OK, that's what I want to do. Find the interpreted code that ends up issuing the READ_BLOCK MLI call, and change the MLI command from $80 to $CC. A one byte patch. Let's go. ~ Chapter 5 One Byte To Rule Them All, And In The Darkness Patch Them ]PR#7 ]CALL -151 ; get the top stack address and put it ; in $0122/$0123 0100- BA TSX 0101- BD 02 01 LDA $0102,X 0104- 8D 23 01 STA $0123 0107- BD 01 01 LDA $0101,X 010A- 8D 22 01 STA $0122 ; increment the address I just copied ; from the stack 010D- EE 22 01 INC $0122 0110- D0 03 BNE $0115 0112- EE 23 01 INC $0123 ; print the address of the current ; opcode, which is stored in ($18) 0115- A5 19 LDA $19 0117- 20 DA FD JSR $FDDA 011A- A5 18 LDA $18 011C- 20 DA FD JSR $FDDA 011F- EA NOP 0120- EA NOP ; check the MLI command 0121- AD FF FF LDA $FFFF ; is it READ_BLOCK? 0124- C9 80 CMP #$80 ; no, branch 0126- D0 03 BNE $012B ; yes, break 0128- 4C 59 FF JMP $FF59 ; continue to real MLI (everything ; other than READ_BLOCK command) 012B- 4C 4B BF JMP $BF4B *PREFIX /MATH.0 *BLOAD MATH.SYSTEM,A$2000,TSYS ; set the trap *BF00:4C 00 01 ; and go *2000G [output before 80-column mode omitted] 1968 73B6 The interpreter is at $73B6 when it issues the raw block read command. That may not be the actual MLI command ID. In fact, it's almost certainly not, since you need to pass a bunch of other parameters with a READ_COMMAND MLI call (unit number, data address, and block number). But it's probably nearby. *73A0.73CF 73A0- 81 0E 05 41 D1 0D 97 0E 73A8- 09 1D D1 0D BB 08 80 00 73B0- FD 1C D1 0D EC 1C 1F 0C 73B8- 88 47 45 54 5F D5 88 73 73C0- F2 0D BB 08 30 BF AA 0D 73C8- 1F 0C 8A 44 45 56 5F CF Bingo. I see an $80 byte at $73AE. I bet that's the MLI command in the interpreted code. Turning to my trusty Block Warden block editor, I look for the surrounding opcodes. Block Warden ["C"hange device -> S5,D1] ["E"dit mode] [earch] ["$08 80 00 FD 1C D1 0D EC"] It finds two matches. Block $0136,offset $14E change 80 to CC Block $0196,offset $027 change 80 to CC ]PR#5 ...works, and it is glorious... Quod erat liberandum. --------------------------------------- A 4am crack No. 486 ------------------EOF------------------