----------Spanish for Mastery---------- A 4am and san inc crack 2022-07-02 --------------------------------------- Name: Spanish for Mastery Genre: educational Year: 1985 Credits: Jean-Paul Valette, Rebecca M. Valette Publisher: D.C. Heath Platform: Apple ][+ or later (64K) Media: 5.25-inch disk Sides: 6 OS: Diversi-DOS C1983 Previous cracks: none This disk was automatically cracked by Passport. Here is the transcript for disk 1A: --v-- Reading from S6,D1 T00,S00 Found Diversi-DOS bootloader Using disk's own RWTS Writing to RAM disk T03 Found nibble count protection track T02,S02 Volume name is C1983 DSR C#254 T00,S05 Found a self-decrypting protection check at $BB03. T00,S05,$03: 4E06BB716E0ABB4027 -> A9B54 8A919484C93B7 Writing to S6,D2 Crack complete. Press any key --^-- More information and source code is available at https://archive.org/details/Passport4am Other sides are unprotected. Quod erat liberand one more thing... ~ Every DOS 3.3-formatted Apple II floppy has a "disk volume number." It is set when you format the disk, is stored in the address field of every sector, and is displayed when you issue the CATALOG command. The disk volume number is almost always ignoreable, except when it isn't. In this program, it isn't. The program disk has the default disk volume number (254), but the data disks are sequentially numbered: - disk 1A: 254 - disk 1B: 101 - disk 2A: 102 - disk 2B: 103 - disk 3A: 104 - disk 3B: 105 And they matter. The program uses the disk volume number to know whether the right disk is in the drive at any particular time. Why is this a problem? In the physical world, it isn't. Unprotected disks can have any disk volume number and still be copyable with third-party tools. But it's the 21st century, and we don't traffic in physical objects; we traffic in disk images. Due to poor historical choices, the dominant format for disk images, .DSK, does not store the disk volume number. This presents us with a modern dilemma of our own design: either release disk images in some other format (easy, but could reduce compatibility or require more disk space or both), or change the game code to distinguish disks in some other way (hard, game-specific, insane, why would you do that). Obviously, we chose the hard way. To distinguish disks that are really .DSK files, we need to store a unique marker somewhere on the disk that will actually be stored in the .DSK file. Since .DSK files only contain sector data from track $00 to track $22, that means we need to store a byte in a sector. That part is non-negotiable. Data must be stored somewhere, and if .DSK files only store sector data, we need to store the fake volume number as sector data. Here's what we're going to do. We're going to store the disk volume number in a sector that has real data, AND intercept the game's low-level disk reading routine so that instead of getting the disk volume from the address field, it gets it from that magic byte in that magic sector, AND intercept low-level format commands to write the disk volume number to that magic byte of that magic sector, AND intercept low-level read commands of that magic sector to return the real sector data that belongs there instead of the magic byte, AND cache the magic byte from the magic sector so we're not actually reading the magic sector for every single disk access (which would totally destroy the performance of an already slow medium). I said it was insane. You were warned. ~ Our code begins at $BEB0. This region is supposed to be part of the format disk routine, but it is now unused for reasons that will be explained shortly. [Note: source code for this routine was provided along with this write-up. If you didn't get it, well, I don't know what to tell you.] [Note the second: qkumba wrote this code. I mostly just cheered.] *BEB0L ; Save address of RWTS parameter table BEB0- 84 48 STY $48 BEB2- 85 49 STA $49 ; Check the requested disk volume BEB4- A0 03 LDY #$03 BEB6- B1 48 LDA ($48),Y ; 0 = wildcard = always continue BEB8- F0 4D BEQ $BF07 BEBA- AA TAX ; Store the requested disk volume ; because we'll need it later BEBB- 8D 01 BF STA $BF01 ; Fake out the real RWTS by putting a 0 ; in the requested disk volume BEBE- A9 00 LDA #$00 BEC0- 91 48 STA ($48),Y ; X holds the requested disk volume -- ; check if it's the same as the last ; RWTS command BEC2- EC 03 BF CPX $BF03 ; Yes, they match, which means we can ; go ahead and execute the RWTS command BEC5- F0 40 BEQ $BF07 ; No, this RWTS command is requesting a ; different disk volume than the last ; RWTS command. It's time for our magic ; act! BEC7- 20 33 BF JSR $BF33 Track $11, sector $01 is the best choice to store our fake disk volume byte, for one reason: it is unused on all disks. (This is common on DOS 3.3.) None of the disks are writeable, so we won't have to intercept writes to this sector, which simplifies the code enormously. *BF33L ; swap out the requested track/sector/ ; command with ; track = $11 ; sector = $01 ; command = $01 (read) ; and save the original values so we ; can restore them later BF33- A2 04 LDX #$04 BF35- BC 4A BF LDY $BF4A,X BF38- B1 48 LDA ($48),Y BF3A- 48 PHA BF3B- BD 49 BF LDA $BF49,X BF3E- 91 48 STA ($48),Y BF40- 68 PLA BF41- 9D 49 BF STA $BF49,X BF44- CA DEX BF45- CA DEX BF46- 10 ED BPL $BF35 BF48- 60 RTS BF49- 01 0C 01 05 11 04 Continuing from $BECA... ; After swapping the RWTS parameters, ; the accumulator has the original RWTS ; command. Check if it's trying to ; format a disk. ; Now, I don't THINK this title ever ; tries to format anything -- it does ; not, for instance, support user data ; disks -- but just to be safe, we're ; going to assume the disk is already ; formatted, and write the disk volume ; as regular data in our magic sector. ; (This code is mostly a holdover from ; a previous title where we used this ; same trick.) ; First, convert $04 (format command) ; to $02 (write command). BECA- C9 04 CMP #$04 BECC- D0 14 BNE $BEE2 BECE- 4A LSR ; Get address from RWTS parameter table BECF- 91 48 STA ($48),Y BED1- A0 08 LDY #$08 BED3- B1 48 LDA ($48),Y BED5- 85 3E STA $3E BED7- C8 INY BED8- B1 48 LDA ($48),Y BEDA- 85 3F STA $3F ; Set the first byte in the buffer to ; 254 BEDC- A0 00 LDY #$00 BEDE- A9 FE LDA #$FE BEE0- 91 3E STA ($3E),Y ; Execution continues here regardless ; (possibly from the branch at $BECC, ; or by falling through after setting ; up the fake format command) BEE2- 20 30 BF JSR $BF30 *BF30L ; call the real RWTS BF30- 20 04 BD JSR $BD04 After the real RWTS returns, $BF30 will fall through to $BF33, which is the routine we called earlier to swap the track/sector/command in the RWTS parameter table. So now they've swapped back to their original values. Continuing from $BEE5... ; Restore read command BEE5- A9 01 LDA #$01 BEE7- 8D 49 BF STA $BF49 ; If the real RWTS came back with an ; error, cancel all the magic and ; propagate the error back to the ; caller BEEA- B0 35 BCS $BF21 ; Real RWTS is OK, so on with the show! ; Y = #$0C when the real RWTS returns, ; which is handy because we want to get ; the RWTS command from the parameter ; table. BEEC- B1 48 LDA ($48),Y BEEE- AA TAX ; I just want to point out that this ; address spells "BEEF", which is funny ; but irrelevant to the task at hand. ; "Where's the $BEEF?" "There it is!" ; Never mind, this entire comment was a ; misteak. BEEF- C8 INY ; Set the error in the RWTS parameter ; table to "disk volume mismatch." ; (This will only be checked by the ; caller if we return with the carry ; bit set, which we haven't decided ; yet, so it's safe to do this now.) BEF0- A9 20 LDA #$20 BEF2- 91 48 STA ($48),Y ; Get the fake disk volume from the ; first byte of the sector data BEF4- A0 00 LDY #$00 BEF6- B1 3E LDA ($3E),Y ; Save it for next time BEF8- 8D 03 BF STA $BF03 ; If the original RWTS command was $04 ; (format), we've done all the magic ; we're going to do today, so tell the ; caller that it worked and be happy. BEFB- E0 04 CPX #$04 BEFD- 18 CLC BEFE- F0 21 BEQ $BF21 ; These next two instructions are self- ; modifying code. $BF01 is the disk ; volume that was originally requested ; (set at $BEBB), and $BF03 is the disk ; volume that we want the caller to ; believe is currently in the drive ; (set at $BEF8). BF00- A9 FE LDA #$FE BF02- C9 FE CMP #$FE ; If they don't match, set the carry to ; indicate an error (we already set the ; RWTS error code to "disk volume ; mismatch, at $BEF2), then exit via ; the cleanup routine at $BF21 which ; will complete the illusion by setting ; the requested and found disk volume ; in the RWTS parameter table. BF04- 38 SEC BF05- D0 1A BNE $BF21 There are 3 possible ways we can end up here. 1) The caller requested a wildcard disk volume ($00), so we branched from $BEB8. 2) The caller requested a disk volume that was the same as the previous RWTS call. We assumed the correct disk is already in the drive and branched here from $BEC5. 3) The caller requested a disk volume that was different from the last RWTS call, we did our magic to get the fake volume from T11,S01 and discovered that the correct disk is now in the drive, so we fell through from $BF05. ; Execute the original RWTS command BF07- 20 04 BD JSR $BD04 ; Didn't work, propagate the error and ; exit BF0A- B0 15 BCS $BF21 Hooray! The original RWTS command succeeded! Just one last thing... ; Was this the game trying to read ; T11,S01? BF0C- A0 04 LDY #$04 BF0E- B1 48 LDA ($48),Y BF10- C9 11 CMP #$11 BF12- D0 0C BNE $BF20 BF14- C8 INY BF15- B1 48 LDA ($48),Y BF17- C9 01 CMP #$01 BF19- D0 05 BNE $BF20 ; Yes, which means the first byte of ; the sector data we just read is the ; fake disk volume. Change that to the ; real data before returning to the ; caller. Presto-chango! BF1B- A9 00 LDA #$00 BF1D- A8 TAY BF1E- 91 3E STA ($3E),Y For this title, all disks have a #$00 as the first byte of the magic sector (T11,S01), so swapping out the real sector data is easy -- it's always 0! But if that weren't the case, we could keep a map of expected data indexed by disk volume. That may be required if we use this code for other titles. This literary technique is called 4shadowing. ; Reset the carry to tell the caller ; that the RWTS command succeeded. (The ; CMPs to check the track and sector ; messed it up.) BF20- 18 CLC All code paths lead here. This is the final cleanup that we always do before returning to the caller: setting the requested and found disk volumes in the RWTS parameter table. This does not affect the carry bit. BF21- A0 03 LDY #$03 BF23- AD 01 BF LDA $BF01 BF26- 91 48 STA ($48),Y BF28- A0 0E LDY #$0E BF2A- AD 03 BF LDA $BF03 BF2D- 91 48 STA ($48),Y BF2F- 60 RTS Now, each disk gets its own magic byte on track $11, sector $01, byte $00 to represent the volume for that disk: disk 1A: T11,S01,$00: 00 -> FE disk 1B: T11,S01,$00: 00 -> 65 disk 2A: T11,S01,$00: 00 -> 66 disk 2B: T11,S01,$00: 00 -> 67 disk 3A: T11,S01,$00: 00 -> 68 disk 3B: T11,S01,$00: 00 -> 69 (nice) Finally, we need one single patch. All RWTS calls go through $BD00. If we change that to jump to $BEB0 instead, the illusion will be complete. T00,S07,$00: 84488549 -> EA4CB0BE Quod erat liberandum. --------------------------------------- A 4am and san inc crack No. 2900 ------------------EOF------------------