-------Addition and Subtraction 1------ A 4am crack 2014-05-10 --------------------------------------- "Blast Off: Addition and Subtraction 1" is a 1985 educational program authored by Thomas Hartsig. It is part of the "Mathematics Courseware Series" distributed by Scott, Foresman and Company. COPYA copies the disk without incident, but the copy does not work. After a fair amount of disk reading, it displays the following message: THIS IS AN ILLEGAL COPY AND WILL NOT RUN. PRESS ANY KEY TO LEAVE. I have decided to ignore this advice. The program appears to be written in Apple Pascal. The boot process starts with several sequential disk reads, then fills the screen with null bytes, then clears the screen and displaying a block cursor in the upper-left corner. Anyone who has ever played Wizardry or GATO will recognize the Apple Pascal boot sequence. Pulling out my trusty Copy ][+ sector editor, I search for the text "ILLEGAL" and find nothing. But searching for the hex sequence "49 4C 4C 45" ("ILLE" without the high bit set) does find the error message on T22,S09. --v-- SECTOR EDITOR DRIVE 1 00- B9 F4 CD 1C 01 04 A5 03 9tM...%. 08- 00 01 00 00 9E 05 B6 01 ......6. 10- 03 0C 00 CD 00 11 9E 00 ...M.... 18- 00 00 CE 02 00 C3 A1 F6 ..N..C!v 20- 00 0A CD 00 1D B6 01 03 ..M..6.. 28- D7 A6 24 54 48 49 53 20 W&$THIS 30- 49 53 20 41 4E 20 49 4C IS AN IL 38- 4C 45 47 41 4C 20 43 4F LEGAL CO 40- 50 59 20 41 4E 44 20 57 PY AND W 48- 49 4C 4C 20 4E 4F 54 00 ILL NOT. 50- CD 00 13 9E 00 B6 01 03 M....6.. 58- CD 00 16 9E 00 B6 01 03 M....6.. 60- 20 00 CD 00 11 9E 00 B6 .M....6 68- 01 03 CD 00 16 9E 00 B6 ..M....6 70- 01 03 D7 A6 1D 52 55 4E ..W&.RUN 78- 2E 20 20 50 52 45 53 53 . PRESS 80- 20 41 4E 59 20 4B 45 59 ANY KEY TRACK $22 SECTOR $09 DOS 3.3 --^-- Unfortunately, the disassembly of the surrounding code makes no sense -- because it's not assembly language! It's p-code. "What is p-code?" I hear you cry. I'm glad you asked. Pascal was not limited to the Apple II. Apple Pascal was an implementation of USCD Pascal, which defined a cross-platform representation of "compiled" Pascal code ("p-code"). Quoting from --v-- What is the UCSD P-System? It is a portable operating system that was popular in the early days of personal computers, in the late 1970s and early 1980s. Like today's [note: this article was written in 2004 -4am] Java, it was based on a "virtual machine" with a standard set of low-level, machine- language-like "p-code" instructions that were emulated on different hardware, including the 6502, the 8080, the Z-80, and the PDP-11. In this way, a Pascal compiler that emitted p-code executables could produce a program that could be run under the P-System on an Apple II, a Xerox 820, or a DEC PDP-11. The most popular language for the P-System was UCSD Pascal. In fact, the P-System operating system itself was written in UCSD Pascal, making the entire operating system relatively easy to port between platforms. By writing a p-code interpreter in the platform's native assembly language, and a few minimal hooks to operating system functions for the file system and interacting with the user, you could move a p-code executable from another system and run it on the new platform. In this way, the p-code generated on one computer could be used to bootstrap the port of the P-System to another computer. --^-- Here endeth the history lesson and starteth the hacking. The first thing I'll need is an Apple Pascal work disk, instead of my usual (DOS 3.3-compatible) Diversi-DOS disk. The second thing is a p-code decompiler from [S6D1=my work disk] [S6D2=original disk] ]PR#6 ... Command: F(ile, E(dit, R(un, C(omp, L(ink, X(ecute, A(ssem, ? [1.3] --> "F" [runs the Apple Pascal file management utility] Filer: L(dir E(dir R(em T(rans C(hng D(ate P(refix K(rnch Z(ero V(ols Q(uit? --> "V" [shows which disks are online] [Confirmed: the original disk is written in Apple Pascal, and it's readable by the standard Apple Pascal filer. Apple Pascal volumes can be referenced by name, like ProDOS volumes. This disk's name is D99AS1:] Volume # - Volume Name - # Blocks 1 CONSOLE: 2 SYSTERM: 4 AS1WORK: 280 5 D99AS1: 280 11 280 System volume is - AS1WORK: Prefix volume is - AS1WORK: --> "L" [lists a volume] Directory listing of what volume ? --> "D99AS1:" [name of original disk] D99AS1: RTSTRP.APPLE 24 25-Dec-83 SYSTEM.PASCAL 28 25-Dec-83 SYSTEM.LIBRARY 39 25-Dec-83 SYSTEM.MISCINFO 1 25-Dec-83 MFILE.DATA 128 14-Aug-80 AGI.CODE 47 16-Apr-84 SYSTEM.STARTUP 2 10-Apr-84 COLOR.DATA 1 29-Mar-84 8/8 files , 276 blocks used, 4 unused, 3 in largest [Under Apple Pascal, "SYSTEM.STARTUP" is the program that runs automatically on boot, like HELLO under DOS 3.3. I'm hoping that's where the copy protection routine lives.] --> "Q" [return to main menu] Command: F(ile, E(dit, R(un, C(omp, L(ink, X(ecute, A(ssem, ? [1.3] --> "X" [execute a program] Execute what file ( to exit) ? --> "DISASSM" [this is the program I downloaded from callapple.org] APPLE PASCAL/6502 DIS-ASSEMBLER VERSION: July 27, 1982 Name of OUTPUT file (default is CONSOLE:): --> "AS1WORK:AS1" Name of code file: --> "D99AS1:SYSTEM.STARTUP" Decode COPIED machine code? [Hmm, that's interesting. Not sure what it means, but yes, let's do that.] --> Y ...a surprisingly short time later... Success! Here is the complete output file, AS1.TEXT: --v-- Code file = D99AS1:SYSTEM.STARTUP The following library bytes are non- zero: COPIED is a linked segment (6502 asmb vers.2), length = 396 bytes These SYSTEM.LIBRARY intrinsics are used: 28 (Chainstuff) ** # PROCS = 2, SEGMENT # = 1 Disassembly for procedure # 2; Lex level = 0 Code length = 117 bytes with 0 .INTERP fixups, 0 .REF fixups, 0 host fixups, and 0 internal fixups. 0000: 68 PLA 0001: 85 00 STA 0000 0003: 68 PLA 0004: 85 01 STA 0001 0006: 68 PLA 0007: 68 PLA 0008: 68 PLA 0009: 68 PLA 000A: 2C E9 C0 BIT 0C0E9 000D: A9 51 LDA #81. 000F: 85 04 STA 0004 0011: C6 06 DEC 0006 0013: D0 1A BNE R002F 0015: C6 04 DEC 0004 0017: D0 16 BNE R002F 0019: A9 00 LDA #0. 001B: F0 4C BEQ R0069 001D: AD EC C0 LDA 0C0EC 0020: 10 FB BPL R001D 0022: C9 D5 CMP #213. 0024: D0 EB BNE R0011 0026: AD EC C0 LDA 0C0EC 0029: 10 FB BPL R0026 002B: C9 AA CMP #170. 002D: D0 E2 BNE R0011 002F: AD EC C0 LDA 0C0EC 0032: 10 FB BPL R002F 0034: C9 96 CMP #150. 0036: D0 D9 BNE R0011 0038: A0 0A LDY #10. 003A: AD EC C0 LDA 0C0EC 003D: 10 FB BPL R003A 003F: C9 FF CMP #255. 0041: D0 CE BNE R0011 0043: 48 PHA 0044: 68 PLA 0045: AD EC C0 LDA 0C0EC 0048: C9 08 CMP #8. 004A: B0 C5 BCS R0011 004C: AD EC C0 LDA 0C0EC 004F: 10 FB BPL R004C 0051: 85 02 STA 0002 0053: 88 DEY 0054: F0 0B BEQ R0061 0056: AD EC C0 LDA 0C0EC 0059: 10 FB BPL R0056 005B: 45 02 EOR 0002 005D: D0 F2 BNE R0051 005F: F0 F0 BEQ R0051 0061: A5 02 LDA 0002 0063: 49 60 EOR #96. 0065: D0 AA BNE R0011 0067: A9 FF LDA #255. 0069: 2C E8 C0 BIT 0C0E8 006C: 48 PHA 006D: 48 PHA 006E: A5 01 LDA 0001 0070: 48 PHA 0071: A5 00 LDA 0000 0073: 48 PHA 0074: 60 RTS 0075: 00 BRK Disassembly for procedure # 1; Lex level = 0 P-code procedure 1 Code = 244, parameters = 4, data = 2 bytes; Jump table = 2 words 0: B9 F4 UJP -12 2: CD 1C 01 CXP 28,1 5: 04 SLDC 4 6: A5 03 LAO 3 8: 00 SLDC 0 9: 01 SLDC 1 10: 00 SLDC 0 11: 00 SLDC 0 12: 9E 05 CSP 5 14: B6 01 03 LOD 1,3 17: 0C SLDC 12 18: 00 SLDC 0 19: CD 00 11 CXP 0,17 22: 9E 00 CSP 0 24: 00 SLDC 0 25: 00 SLDC 0 26: CE 02 CLP 2 28: 00 SLDC 0 29: C3 EQUI 30: A1 F6 FJP -10 32: 00 SLDC 0 33: 0A SLDC 10 34: CD 00 1D CXP 0,29 37: B6 01 03 LOD 1,3 40: D7 NOP 41: A6 24 54 48 49 53 20 49 53 20 LSA 36,"THIS IS AN ILLEGAL COPY AND WIL L NOT" 79: 00 SLDC 0 80: CD 00 13 CXP 0,19 83: 9E 00 CSP 0 85: B6 01 03 LOD 1,3 88: CD 00 16 CXP 0,22 91: 9E 00 CSP 0 93: B6 01 03 LOD 1,3 96: 20 SLDC 32 97: 00 SLDC 0 98: CD 00 11 CXP 0,17 101: 9E 00 CSP 0 103: B6 01 03 LOD 1,3 106: CD 00 16 CXP 0,22 109: 9E 00 CSP 0 111: B6 01 03 LOD 1,3 114: D7 NOP 115: A6 1D 52 55 4E 2E 20 20 50 52 LSA 29,"RUN. PRESS ANY KEY TO LEAVE." 146: 00 SLDC 0 147: CD 00 13 CXP 0,19 150: 9E 00 CSP 0 152: B6 01 03 LOD 1,3 155: CD 00 16 CXP 0,22 158: 9E 00 CSP 0 160: 00 SLDC 0 161: 16 SLDC 22 162: CD 00 1D CXP 0,29 165: B6 01 02 LOD 1,2 168: CD 00 15 CXP 0,21 171: 9E 00 CSP 0 173: 01 SLDC 1 174: 01 SLDC 1 175: 9E 04 CSP 4 177: A6 03 41 47 49 LSA 3, "AGI" 182: D7 NOP 183: CD 1C 02 CXP 28,2 186: 19 SLDC 25 187: 16 SLDC 22 188: CD 00 1D CXP 0,29 191: B6 01 03 LOD 1,3 194: D7 NOP 195: A6 0C 50 4C 45 41 53 45 20 57 LSA 12,"PLEASE WAIT." 209: 00 SLDC 0 210: CD 00 13 CXP 0,19 213: 9E 00 CSP 0 215: B6 01 03 LOD 1,3 218: CD 00 16 CXP 0,22 221: 9E 00 CSP 0 223: 29 SLDC 41 224: 00 SLDC 0 225: CD 00 1D CXP 0,29 228: 01 SLDC 1 229: 01 SLDC 1 230: 9E 04 CSP 4 232: 1C SLDC 28 233: 9E 16 CSP 22 235: B9 05 UJP 5 237: 1C SLDC 28 238: 9E 15 CSP 21 240: B9 F2 UJP -14 242: C1 00 RBP 0 Jump table: 244: F2 00 .WORD R2 (Entry # -14) 246: 09 00 .WORD R237 (Entry # -12) 248: 47 00 .WORD R177 (Entry # -10) --^-- Wow. That seems like a lot of useful information, but what does it tell me? I am by no means an expert here. This is my first foray into Pascal hacking and decompiling. (I do recognize the nibble check in procedure #2, though. I can smell those a mile away.) Here are some references on Apple Pascal hacking that I found helpful: The "Apple Pascal Operating System Reference Manual", currently available at Appendices A and B of that manual are available in plain text at "P-Source: A Guide to the Apple Pascal System" by Randall Hyde, currently available at In 2012, "Tommy" wrote a series of posts on comp.sys.apple2 (yes, the Usenet newsgroup) called "Re-engineered: Wizardry III, Legacy of Llylgamyn", currently available at In 2014, "Tommy" was encouraged to write another series on comp.sys.apple2 called "Wizardry re-engineering", currently available at Armed with just enough information to be dangerous, I set out on an adventure of Pascal decompiling! Under the "Disassembly of procedure 1", the first instruction looks like this: 0: B9 F4 UJP -12 According to Appendix A in the Apple Pascal Operating System Reference Manual (hereafter known as "the f---ing manual"), "UJP" is an unconditional jump. Unlike 6502 assembly language, all jumps are referenced by their position in a "jump table" at the end of the procedure. This is the jump table for this procedure: Jump table: 244: F2 00 .WORD R2 (Entry # -14) 246: 09 00 .WORD R237 (Entry # -12) 248: 47 00 .WORD R177 (Entry # -10) So the instruction "UJP -12" jumps to the address listed as "Entry # -12", which is R237. "237" refers to the instruction at byte offset 237, which is listed near the end of the procedure: 237: 1C SLDC 28 238: 9E 15 CSP 21 240: B9 F2 UJP -14 According to the f---ing manual, "SLDC 28" pushes the decimal value 28 ($1C) to the stack. "CSP" stands for "call standard procedure." Apple Pascal has a number of built-in functions like "cos" (calculate a cosine) or "readln" (read a line of input). But what is standard procedure #21? The f---ing manual does not say. But Hyde's book does; page 331 has the entire CSP table, and the 21st entry is "LDS" ("Load Segment"). Quoting Hyde: "[LDS] pops the segment number off of the stack, gets it into the 6502 accumulator, and then calls the load segment subroutine. Upon return from load segment, control is returned to the main interpreter loop." OK, but what segment are we loading? #28, which was just pushed to the stack in the previous instruction. But what is segment #28? According to Appendix A in the f---ing manual, segment 0 and segments 2-6 are reserved for the operating system. Segment 1 is the main program that is currently executing (SYSTEM.STARTUP in this case). Segments 7-21 are for segments and units defined within the program. And segments 22-31 are predefined intrinsic units. Apple Pascal includes several libraries like TURTLEGRAPHICS for Logo-like graphic commands and APPLESTUFF for access to joystick buttons and other hardware. But which one is #28? I finally found the answer in one of Tommy's posts in his "Wizardry re- engineering" series. Using a tool called LIBMAP, Tommy dug into the SYSTEM.LIBRARY file (part of the Apple Pascal operating system) and found the internal segment numbers for each intrinsic unit in Apple Pascal. 20 TURTLEGRAPHICS (Logo-like graphics) 22 APPLESTUFF (hardware access) 28 CHAINSTUFF (launching programs) 29 TRANSCEND (geometry functions) 30 LONGINTIO (long integer support) 31 PASCALIO (parsing decimals) Thus, the p-code instruction "SLDC 28", followed by "CSP 21", will load the CHAINSTUFF intrinsic unit from SYSTEM.LIBRARY. "UJP -14" is an unconditional jump to the address listed in the jump table under Entry # -14, which is R2. So that means we're jumping to the instruction at byte offset 2, which is right after the first unconditional jump. 2: CD 1C 01 CXP 28,1 "CXP" stands for "Call eXternal Procedure", and the f---ing manual says it takes two arguments, a segment # and a procedure # (in that order). According to Tommy, procedure #28 in segment #1 is "uses chainstuff". That makes sense, I suppose, since we just got finished loading that library into memory. Now we're signaling that we want to use it. That's it. Four hours of research, and "uses chainstuff" is as far as I've gotten. Did I mention I have no idea what I'm doing? This is fun. Are you having fun? I'm having fun. Moving on. 5: 04 SLDC 4 6: A5 03 LAO 3 8: 00 SLDC 0 9: 01 SLDC 1 10: 00 SLDC 0 11: 00 SLDC 0 12: 9E 05 CSP 5 According to the f---ing manual, "SLDC 4" just pushes the value 4 to the stack. (To conserve space, p-code has lots of one-byte instructions that all mean "push me to the stack.") "LAO" is "load global address". The f---ing manual has this to say about that: "LAO B. Fetch the word with offset 'B' in BASE activation record and push it." Then more pushing, then "CSP 5". According to Hyde, standard procedure #5 is "UREAD" (unit read). According to Tommy, the unit read procedure is a low-level disk read, like calling the RWTS directly under DOS 3.3. It takes 5 parameters. The first parameter is a volume number. In Apple Pascal, the volume number of the disk you're booting from is always 4. (I saw this earlier when I listed all online volumes from the Filer.) That explains the "SLDC 4" and "LAO 3" instructions. It's getting a reference to volume #4 and pushing it to the stack, in preparation for the unit read procedure to pull it off the stack and use the value as its first parameter. Damn, this is slow going. Anyway, if I'm right that the embedded assembly language procedure is a nibble check, then this Pascal code makes sense. It needs to position the drive head properly before it can do the nibble check, since the special nibble sequence is usually only stored in one place on the disk. 14: B6 01 03 LOD 1,3 17: 0C SLDC 12 18: 00 SLDC 0 19: CD 00 11 CXP 0,17 According to Tommy, procedure #17 in segment 0 is "WRITE()". As in, write things on the screen. I'm not sure what it's writing, but I'm going to optimistically skip over it and hope it isn't important. 22: 9E 00 CSP 0 According to Hyde, standard procedure 0 is IOCHECK. It checks the result of the last I/O operation and triggers a run-time error if it's non-zero. I'm assuming this is checking the result of the unit read procedure that positioned the drive head. 24: 00 SLDC 0 25: 00 SLDC 0 26: CE 02 CLP 2 The f---ing manual says that "CLP" standards for "call local procedure." From the decompiler listing I got, local procedure #2 is the embedded assembly language routine -- i.e. the nibble check. This is it! This is where it calls the nibble check! 28: 00 SLDC 0 29: C3 EQUI "EQUI" is an "is-equals" test. It compares the top value on the stack with the next-to-top value on the stack. The top value is 0, since the instruction just before it (at offset 28) was "SLDC 0". The next-to-top value is the return value from calling local procedure #2. So I think this is saying "if the nibble check returned 0, then." Then what? 30: A1 F6 FJP -10 "FJP" stands for "false jump," or less confusingly, "jump if false." If the previous test evaluated to false, then jump. The previous test was "did the nibble check return 0?" So I had it backwards in the previous paragraph. The proper prose description of this code is "if the nibble check did not return 0, then jump." Jump to where? To the offset listed in the jump table under Entry #-10, which is R177. Without decompiling every single instruction between 30 and 177, it seems pretty clear that this is the branch we want to take (but don't, because the nibble check fails and returns 0). In between 30 and 177, I see the exact error message about "THIS IS AN ILLEGAL COPY" blah blah whatever. This jump-if-false branches right over all of that and goes on with the real business of SYSTEM.STARTUP, which is presumably to use the intrinsic library CHAINSTUFF to launch the main program. If I change that "EQUI" (is-equals) instruction at offset 29 to a "NEQI" (is-not-equals) instruction, that would reverse the logic entirely. Instead of saying, "if the nibble check failed, display this message and hang," it would say, "if the nibble check succeeds, display an error message and hang." I like this solution for two reasons: first, it's the simplest thing that could possibly work. It's only a one- byte change, so it doesn't require inserting or removing any bytes of the p-code (which is convoluted and intimidating, to say the least). And second, I enjoy the irony of a program that does a nibble check but complains about success instead of failure. Whipping out my trusty Copy ][+ sector editor, I go back to the sector where I originally found the error message and confirm that the surrounding bytes are identical to the p-code that I've been decompiling. With a mix of trepidation and optimism, I make one small change: T22,S09,$1D change "C3" to "CB" It worked! It actually worked! The copy boots and runs! I have two other disks by the same company that use the same copy protection routine: - Addition and Subtraction 2 T0C,S0B,$1D change "C3" to "CB" - Frog Jump: Ordering Numbers T0C,S0B,$1D change "C3" to "CB" Quod erat liberandum. --------------------------------------- A 4am crack No. 31 ------------------EOF------------------