Introduction
One of the early forms of copy protection was a “doc check”. It was a prompt to enter in a certain word or page number from the game manual. Photocopier-proof sheets and even code wheels were sometimes packaged with games. Not only was this copy protection annoying, since it had to be entered on each playthrough, but preserving the needed reference materials can be a pain decades later. Using a modern emulator, QEMU, and MacsBug, we are going to completely bypass the copy protection (a “clean” crack) for Where in Time is Carmen Sandiego?.
Tools
This project uses MacsBug 6.6.3 and Resorcerer 1.2.5. For more details on their installation and usage, check out Tutorial 001. We will also use QEMU — refer to its documentation for setting up OS 9. Be sure to enable the QEMU Monitor console with -monitor stdio.
Save The State
Once we get to the end of the first level, just after catching the crook — maybe during the arrest animation, save the state in QEMU with savevm carmen1 (carmen1 can be any name you like). We will restore back to this state throughout this tutorial.
Investigation
After a few clicks, the copy protection screen should appear. In previous tutorials, we tried to figure out the Toolbox trap used in a dialog box for a serial number. Some doc checks use a standard dialog box and this would be a good strategy for those instances, but here we definitely are not going to be using GetDialogItemText. We could try StringToNum or GetKeys, but notice that after entering in five wrong answers, the game quits.

Reverse Engineering in Reverse
Since the game quits right from the copy protection screen, let’s drop into MacsBug using nmi in QEMU. Once in, atb ExitToShell. After entering a wrong guess five times, the game should try and quit and break into MacsBug. From there, ip to disassemble around the current instruction:
Disassembling from 0E25771A 'CODE 0005 00B2' +00CDA 0E25771A DC.W $F632 ; ???? | F632 +00CDC 0E25771C RTS | 4E75 +00CDE 0E25771E MOVEQ #$01,D0 | 7001 +00CE0 0E257720 MOVE.B D0,-$09CF(A5) | 1B40 F631 +00CE4 0E257724 JSR 'CODE 0005 00B2'+00CD2 ; 0E257712 | 4EBA FFEC +00CE8 0E257728 MOVE.W #$FFFF,-(A7) | 3F3C FFFF +00CEC 0E25772C CLR.W -(A7) | 4267 +00CEE 0E25772E JSR 'CODE 0009 00B2'+01B0E | 4EAD 0952 +00CF2 0E257732 *_ExitToShell ; 0031B22E | A9F4 +00CF4 0E257734 RTS | 4E75 +00CF6 0E257736 CLR.L -(A7) | 42A7 +00CF8 0E257738 _FrontWindow ; 00959EA4 | A924 +00CFA 0E25773A MOVEA.L (A7)+,A0 | 205F +00CFC 0E25773C CMPA.L -$0A8C(A5),A0 | B1ED F574 +00D00 0E257740 SEQ D0 | 57C0 +00D02 0E257742 NEG.B D0 | 4400 +00D04 0E257744 EXT.W D0 | 4880 +00D06 0E257746 RTS | 4E75 +00D08 0E257748 LINK A6,#$FFF8 | 4E56 FFF8 +00D0C 0E25774C CLR.L -(A7) | 42A7
The current subroutine is quite short and begins at +00CDE. We can tell this because there is an RTS right above at +00CDC. What we want to do is figure out the place in memory that called this Exit routine. We can do this by noting the memory address of the first instruction: 0E25771E. This number will likely change if the game is reopened, but because we saved the QEMU state, this memory location remains the same if we were to restore the state.
Restore the state in QEMU using loadvm carmen1. We are back just before the copy protection screen, but now drop into MacsBug and bp 0E25771E. After five wrong guesses we should now be at the entry point a few lines above ExitToShell. If this is the case, the return address should be the first address on the stack. We can verify this with dm A7 and ip @A7:
Displaying memory from A7 0E3D4EE4 0E25 41CA 4EF8 0E3D 4EF8 0E25 20C8 0004
Disassembling from @A7 'CODE 0006 00B2' +032A2 0E2541B2 BEQ.S 'CODE 0006 00B2'+032BA ; 0E2541CA | 6716 +032A4 0E2541B4 JSR 'CODE 0002 00B2'+002CC | 4EAD 0492 +032A8 0E2541B8 TST.B D0 | 4A00 +032AA 0E2541BA BNE.S 'CODE 0006 00B2'+032BA ; 0E2541CA | 660E +032AC 0E2541BC JSR 'CODE 0006 00B2'+026F2 ; 0E253602 | 4EBA F444 +032B0 0E2541C0 CMPI.W #$FFFF,D0 | 0C40 FFFF +032B4 0E2541C4 BNE.S 'CODE 0006 00B2'+032BA ; 0E2541CA | 6604 +032B6 0E2541C6 JSR 'CODE 0005 00B2'+00CDE | 4EAD 0742 +032BA 0E2541CA JSR 'CODE 0006 00B2'+02B54 ; 0E253A64 | 4EBA F898 +032BE 0E2541CE JSR 'CODE 0006 00B2'+02AB2 ; 0E2539C2 | 4EBA F7F2 +032C2 0E2541D2 TST.W D0 | 4A40 +032C4 0E2541D4 BEQ.S 'CODE 0006 00B2'+032CC ; 0E2541DC | 6706 +032C6 0E2541D6 JSR 'CODE 0006 00B2'+0303C ; 0E253F4C | 4EBA FD74 +032CA 0E2541DA BRA.S 'CODE 0006 00B2'+032D6 ; 0E2541E6 | 600A +032CC 0E2541DC JSR 'CODE 0006 00B2'+03086 ; 0E253F96 | 4EBA FDB8 +032D0 0E2541E0 BRA.S 'CODE 0006 00B2'+032D6 ; 0E2541E6 | 6004 +032D2 0E2541E2 JSR 'CODE 0006 00B2'+0581C ; 0E25672C | 4EBA 2548 +032D6 0E2541E6 JSR 'CODE 0006 00B2'+02AB2 ; 0E2539C2 | 4EBA F7DA +032DA 0E2541EA TST.W D0 | 4A40 +032DC 0E2541EC BEQ.S 'CODE 0006 00B2'+032E6 ; 0E2541F6 | 6708
The address that called the routine should be exactly four bytes less than the return address. Sure enough, the calling line is: +032B6 0E2541C6 JSR 'CODE 0005 00B2'+00CDE. Note that 'CODE 0005 00B2'+00CDE matches the entry point of the Exit routine. Around the line, there are a few branches to the return address, 0E2541CA. Looking at the closest branch at +032B4, if D0 is not equal to FFFF (CMPI.W #$FFFF,D0), branch to the return address, bypassing the Exit routine.
Let’s note the address of the comparison for the branch, 0E2541C0, restore the state with loadvm carmen1, and set a breakpoint on it with bp 0E2541C0. After entering five guesses, MacsBug should open and D0 should contain FFFF. This value will cause the branch to fall through, running the Exit routine. Set D0=1 and g to resume execution.
It works! The game does not quit and you can proceed to the next level. This would be a working crack, but not a clean one. Not only does the doc check screen come up, but it also requires you to enter five wrong guesses. We can do better.
A Clean Crack
Recall our disassembly around the call to the Exit routine at 0E2541CA in the previous listing. The branch above our breakpoint at 0E2541C0 was at +032A8. There, if D0 is non-zero (TST.B D0 / BNE.S), bypass the Exit routine. What about above that at +032A2? Let’s ip 0E2541B2:
Disassembling from 0E254194 'CODE 0006 00B2' +03284 0E254194 ADDQ.L #$2,A7 | 548F +03286 0E254196 CMPI.W #$0004,$0008(A6) | 0C6E 0004 0008 +0328C 0E25419C BNE.S 'CODE 0006 00B2'+032D2 ; 0E2541E2 | 6644 +0328E 0E25419E JSR 'CODE 0002 00B2'+0030C | 4EAD 04E2 +03292 0E2541A2 TST.B D0 | 4A00 +03294 0E2541A4 BNE.S 'CODE 0006 00B2'+032D6 ; 0E2541E6 | 6640 +03296 0E2541A6 JSR 'CODE 0006 00B2'+02AC2 ; 0E2539D2 | 4EBA F82A +0329A 0E2541AA MOVEA.L -$3C92(A5),A0 | 206D C36E +0329E 0E2541AE TST.B $000A(A0) | 4A28 000A +032A2 0E2541B2 BEQ.S 'CODE 0006 00B2'+032BA ; 0E2541CA | 6716 +032A4 0E2541B4 JSR 'CODE 0002 00B2'+002CC | 4EAD 0492 +032A8 0E2541B8 TST.B D0 | 4A00 +032AA 0E2541BA BNE.S 'CODE 0006 00B2'+032BA ; 0E2541CA | 660E +032AC 0E2541BC JSR 'CODE 0006 00B2'+026F2 ; 0E253602 | 4EBA F444 +032B0 0E2541C0 CMPI.W #$FFFF,D0 | 0C40 FFFF +032B4 0E2541C4 BNE.S 'CODE 0006 00B2'+032BA ; 0E2541CA | 6604 +032B6 0E2541C6 JSR 'CODE 0005 00B2'+00CDE | 4EAD 0742 +032BA 0E2541CA JSR 'CODE 0006 00B2'+02B54 ; 0E253A64 | 4EBA F898 +032BE 0E2541CE JSR 'CODE 0006 00B2'+02AB2 ; 0E2539C2 | 4EBA F7F2 +032C2 0E2541D2 TST.W D0 | 4A40
At +0329E there is a check if $000A(A0) is zero (TST.B $000A(A0)) and a branch if it is (BEQ.S). Since there are no other branches to the return address 0E2541CA nearby, this seems like another good place to set a breakpoint with bp 0E2541AE. Upon restoring the state with loadvm carmen1 and proceeding to the copy protection screen, it drops into MacsBug instead. Use t to step to the branch, but simulate taking the branch with pc=0E2541CA. This sets the program counter, the active memory location, as if we had taken the branch. After g to resume, the game moves on to the next level with no copy protection in sight!
Creating a Cracked Application
Now the clean crack is clear, we have to set a branch always (BRA) at CODE 6 +032A2. To do this, open the Carmen Time application in Resorcerer. Open the CODE 6 resource then Code > Goto/Find/Replace…. Type in the offset 32A2:

Now highlight the 67 in 6716 and change it to 60. The branch instruction should change to BRA accordingly:

Save your work and test it out by playing through the first level.
Conclusion
When reverse engineering, working backwards can be faster and easier than working forwards. Instead of catching ExitToShell, sometimes you’ll want to catch an alert with Alert, NoteAlert, CautionAlert, or StopAlert and work back from there. These traps are useful when creating NoCDs since there is likely a prompt to insert a CD. QEMU save states are another tool we can use to crack applications because memory addresses are preserved. With fixed memory addresses, setting breakpoints at key places is trivial, and are able to quickly give clarity to complex code. A clean crack makes gameplay seamless and that’s always the ultimate goal when cracking.