-----------The Black Cauldron---------- A 4am and san inc crack 2018-01-14 --------------------------------------- Name: The Black Cauldron Genre: adventure Year: 1985 Credits: game design by Roberta Williams, Al Lowe, and the walt Disney Personal Computer Software Design and Development Staff; programming by Sol Ackerman, Chris Iden, Scott Murphy, Jeff Stephenson, Ken Williams, Al Lowe; graphics by Mark Crowe Publisher: Sierra On-Line Platform: Apple //e or later (128K) Media: 3 double-sided 5.25-inch disks (5 sides total) OS: DOS 3.3 This disk was automatically cracked by Passport, using qkumba's universal Sierra patcher. Here is the transcript for disk 1A: --v-- READING FROM S6,D1 T00,S00 FOUND DOS 3.3 BOOTLOADER USING DISK'S OWN RWTS WRITING TO S5,D2 T11,S0F FOUND SIERRA PROTECTION CHECK T11,S0F,$09: 20 -> 60 T02,S02 VOLUME NAME IS DISK VOLUME 001 CRACK COMPLETE. --^-- 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. Passport now finds and logs the disk volume number on DOS 3.3 disks (as seen above). The disk volume number is almost always ignoreable, except when it isn't. In this game, it isn't. The boot disk has disk volume number 001, as shown in the Passport log. The other disks also have custom disk volume numbers: - disk 1B: 002 - disk 2A: 003 - disk 2B: 004 - disk 3A: 005 - data disks (created within the game by pressing "4"): 015 And they matter. The game 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 2018, and we don't traffic in physical objects anymore; 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. Of course, the game doesn't make this easy for us. There isn't actually one sector that is unused across all disks. (Trust me, we checked.) But that's not even the worst part. This game has an in-game command to format a data disk. The data disk is formatted with -- you guessed it -- a nonstandard disk volume number, which is later checked to ensure the data disk is in the drive during the "SAVE GAME" or "RESTORE GAME" command. 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. Caveat emptor.] [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 57 BEQ $BF11 ; Otherwise check if it's $FE BEBA- AA TAX BEBB- E8 INX BEBC- E0 FF CPX #$FF ; No, it's a non-standard disk volume BEBE- 90 02 BCC $BEC2 ; Yes, it was $FE -- change it to $01 ; to fake out the caller (the game will ; refuse to boot on a disk with the ; wrong disk volume, because f--- you) BEC0- A9 01 LDA #$01 ; Either way, store the requested disk ; volume because we'll need it later BEC2- 8D 0B BF STA $BF0B ; If we're trying to read the boot disk, ; skip ahead (the carry is still set ; from the CMP at $BEBC) BEC5- B0 4A BCS $BF11 ; The other disks need special handling ; because they can be requested at any ; time. First off, fake out the real ; RWTS by putting a 0 in the requested ; disk volume BEC7- A9 00 LDA #$00 BEC9- 91 48 STA ($48),Y BECB- CA DEX ; X holds the requested disk volume -- ; check if it's the same as the last ; RWTS command BECC- EC 0D BF CPX $BF0D ; Yes, they match, which means we can ; go ahead and execute the RWTS command BECF- F0 40 BEQ $BF11 ; No, this RWTS command is requesting a ; different disk volume than the last ; RWTS command. It's time for our magic ; act! BED1- 20 3D BF JSR $BF3D Track $22, sector $0F is the best choice to store our fake disk volume byte, for one reason: it is unused on the save game disk. None of the other disks are writeable, so we won't have to intercept writes to the sector, which simplifies the code enormously. *BF3DL ; swap out the requested track/sector/ ; command with ; track = $22 ; sector = $0F ; command = $01 (read) ; and save the original values so we ; can restore them later BF3D- A2 04 LDX #$04 BF3F- BC 54 BF LDY $BF54,X BF42- B1 48 LDA ($48),Y BF44- 48 PHA BF45- BD 53 BF LDA $BF53,X BF48- 91 48 STA ($48),Y BF4A- 68 PLA BF4B- 9D 53 BF STA $BF53,X BF4E- CA DEX BF4F- CA DEX BF50- 10 ED BPL $BF3F BF52- 60 RTS BF53- 01 0C 0F 05 22 04 Continuing from $BED4... ; After swapping the RWTS parameters, ; the accumulator has the original RWTS ; command. Check if it's a format ; command -- i.e. the game is trying to ; initialize a disk for saved games. BED4- C9 04 CMP #$04 ; no (whew) BED6- D0 14 BNE $BEEC ; Yes, this is a format command. Ugh. ; We're not really going to format the ; disk. Instead, we're going to assume ; the disk is already formatted, and ; write the disk volume as regular data ; in our special sector (T22,S0F). ; First, convert $04 (format command) ; to $02 (write command). BED8- 4A LSR BED9- 91 48 STA ($48),Y ; Get address from RWTS parameter table BEDB- A0 08 LDY #$08 BEDD- B1 48 LDA ($48),Y BEDF- 85 3E STA $3E BEE1- C8 INY BEE2- B1 48 LDA ($48),Y BEE4- 85 3F STA $3F ; The expected disk volume of a save ; game disk is $0F, so set that as the ; first byte in the buffer BEE6- A0 00 LDY #$00 BEE8- A9 0F LDA #$0F BEEA- 91 3E STA ($3E),Y ; Execution continues here regardless ; (possibly from the branch at $BED6, ; or by falling through after setting ; up the fake format command) BEEC- 20 3A BF JSR $BF3A *BF3AL ; call the real RWTS BF3A- 20 04 BD JSR $BD04 After the real RWTS returns, $BF3A will fall through to $BF3D, 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 $BEEF... ; 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- A9 01 LDA #$01 ; Restore read command BEF1- 8D 53 BF STA $BF53 ; If the real RWTS came back with an ; error, cancel all the magic and ; propagate the error back to the ; caller BEF4- B0 35 BCS $BF2B ; 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. BEF6- B1 48 LDA ($48),Y BEF8- AA TAX ; 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.) BEF9- C8 INY BEFA- A9 20 LDA #$20 BEFC- 91 48 STA ($48),Y ; Get the fake disk volume from the ; first byte of the sector data BEFE- A0 00 LDY #$00 BF00- B1 3E LDA ($3E),Y ; Save it for next time BF02- 8D 0D BF STA $BF0D ; 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. BF05- E0 04 CPX #$04 BF07- 18 CLC BF08- F0 21 BEQ $BF2B ; These next two instructions are self- ; modifying code. $BF0B is the disk ; volume that was originally requested ; (set at $BEC2), and $BF0D is the disk ; volume that we want the caller to ; believe is currently in the drive ; (set at $BF02). BF0A- A9 01 LDA #$01 BF0C- C9 01 CMP #$01 ; If they don't match, set the carry to ; indicate an error (we already set the ; RWTS error code to "disk volume ; mismatch, at $BEFC), then exit via ; the cleanup routine at $BF2B which ; will complete the illusion by setting ; the requested and found disk volume ; in the RWTS parameter table. BF0E- 38 SEC BF0F- D0 1A BNE $BF2B There are 4 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 disk volume $FE, so we branched from $BEC5. 3) The caller requested a disk volume of one of the game disks ($01-$05, or $0F to read from the save game disk), but it was the same disk volume as the previous RWTS call. We assume the correct disk is already in the drive and branched here from $BECF. 4) The caller requested a disk volume of one of the game disks, it was different from the previous RWTS call, we did our magic to get the fake volume from T22,S0F and discovered that the correct disk is now in the drive, so we fell through from $BF0F. ; Execute the original RWTS command BF11- 20 04 BD JSR $BD04 ; Didn't work, propagate the error and ; exit BF14- B0 15 BCS $BF2B Hooray! The original RWTS command succeeded! Just one last thing... ~ ; Was this the game trying to read ; T22,S0F? BF16- A0 04 LDY #$04 BF18- B1 48 LDA ($48),Y BF1A- C9 22 CMP #$22 BF1C- D0 0C BNE $BF2A BF1E- C8 INY BF1F- B1 48 LDA ($48),Y BF21- C9 0F CMP #$0F BF23- D0 05 BNE $BF2A ; 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! BF25- A9 00 LDA #$00 BF27- A8 TAY BF28- 91 3E STA ($3E),Y [In this game, all disks have a #$00 as the first byte of T22,S0F, so swapping out the real sector data is easy -- it's always #$00! 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 games.] [Hint: we are definitely going to use this code for other games.] [This 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.) BF2A- 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. BF2B- A0 03 LDY #$03 BF2D- AD 0B BF LDA $BF0B BF30- 91 48 STA ($48),Y BF32- A0 0E LDY #$0E BF34- AD 0D BF LDA $BF0D BF37- 91 48 STA ($48),Y BF39- 60 RTS Now, each disk gets its own magic byte on track $22, sector $0F: disk 1A: T22,S0F,$00: 00 -> 01 disk 1B: T22,S0F,$00: 00 -> 02 disk 2A: T22,S0F,$00: 00 -> 03 disk 2B: T22,S0F,$00: 00 -> 04 disk 3A: T22,S0F,$00: 00 -> 05 Finally, we need one single patch. All RWTS calls go through $B7B5, which in turn calls $BD00. If we change that to call $BEB0 instead, the illusion will be complete. T00,S01,$B8: 00BD -> B0BE Quod erat liberandum. --------------------------------------- A 4am and san inc crack No. 1612 ------------------EOF------------------