💾 Archived View for mirrors.apple2.org.za › active › 4am › images › games › simulation › Tomahawk%20… captured on 2024-05-26 at 17:37:31.

View Raw

More Information

⬅️ Previous capture (2023-01-29)

-=-=-=-=-=-=-

----------------Tomahawk---------------
A 4am crack                  2016-08-01
---------------------------------------

Name: Tomahawk
Genre: simulation
Year: 1987
Author: John Brooks
Publisher: Datasoft
Platform: Apple ][+ or later (64K)
Media: single-sided 5.25-inch floppy
OS: custom
Similar cracks:
  #177 Video Title Shop

                   ~

               Chapter 0
 In Which Various Automated Tools Fail
          In Interesting Ways


COPYA
  no errors, but copy boots to title
  screen then says "PLEASE USE ORIGINAL
  DISK" and hangs

Locksmith Fast Disk Backup
  ditto

EDD 4 bit copy (no sync, no count)
  ditto

Copy ][+ nibble editor
  nothing suspicious

Disk Fixer
  bootloader is custom, no disk catalog

Why didn't any of my copies work?
  There's some kind of protection check
  during late boot.

Next steps:

  1. Trace the boot
  2. Disable the protection check
  3. Declare victory(*)

(*) take a nap

                   ~

               Chapter 1
   In Which The Tools Do Not Save Us


Let's see if AUTOTRACE can make heads
or tails of it.

[S6,D1=original disk]
[S5,D1=my work disk]

]PR#5
CAPTURING BOOT0
...reboots slot 6...
...reboots slot 5...
SAVING BOOT0

Drat.

Gonna have to do this the hard way.

]CALL -151



0801-   4C 7E 08    JMP   $087E



087E-   A2 79       LDX   #$79
0880-   BD 04 08    LDA   $0804,X
0883-   48          PHA
0884-   4A          LSR
0885-   68          PLA
0886-   6A          ROR
0887-   9D 04 08    STA   $0804,X
088A-   CA          DEX
088B-   D0 F3       BNE   $0880
088D-   4C 14 08    JMP   $0814

A decryption loop for the first half
of the sector (more or less). It looks
like it's self-contained (not dependent
on any initial state), so I should just
be able to run it and see the decrypted
code.



Well, the good news is that it worked.
The bad news is... well, see for
yourself.



; ends up with $40 in $0801, which is
; the little-used "RTI" instruction
; (more on this later)
0814-   A9 0C       LDA   #$0C
0816-   4D 01 08    EOR   $0801
0819-   8D 01 08    STA   $0801

; save boot slot (x16) in zero page $DF
081C-   A6 2B       LDX   $2B
081E-   8A          TXA
081F-   85 DF       STA   $DF

; munge boot slot (weird)
0821-   0A          ASL
0822-   0A          ASL
0823-   2A          ROL
0824-   2A          ROL
0825-   2A          ROL
0826-   49 C0       EOR   #$C0
0828-   8D 08 08    STA   $0808

; set up zero page to reuse disk
; controller ROM routine to read more
; sectors
082B-   A0 03       LDY   #$03
082D-   B9 05 08    LDA   $0805,Y
0830-   99 27 00    STA   $0027,Y
0833-   B9 09 08    LDA   $0809,Y
0836-   99 3E 00    STA   $003E,Y
0839-   88          DEY
083A-   10 F1       BPL   $082D

; at this point, $28 has the value from
; $0806, which was $06
083C-   A4 28       LDY   $28

; $080D is a table of physical sectors
; to read
083E-   B9 0D 08    LDA   $080D,Y
0841-   A0 00       LDY   #$00

; At this point, $40 has the value from
; $080B/$080C, which is $003D. Multiple
; levels of indirection going on here.
0843-   91 40       STA   ($40),Y

; $0805 starts at $45 and is used as
; the target address (high byte)
0845-   AD 05 08    LDA   $0805
0848-   85 27       STA   $27

; ...and decremented
084A-   CE 05 08    DEC   $0805

; a sector count (starts at $06)
084D-   CE 06 08    DEC   $0806

; break out of sector read loop
0850-   30 0F       BMI   $0861

; set up a return address on the stack
0852-   A9 08       LDA   #$08
0854-   48          PHA
0855-   A9 5E       LDA   #$5E
0857-   48          PHA

; ...and one more byte (WTF)
0858-   A9 00       LDA   #$00
085A-   48          PHA

; At this point, $29/$2A have the value
; from $0807/$0808, except that $0808
; was modified earlier based on the
; munged boot slot. It ends up being
; $C65C if the boot slot was 6.
085B-   6C 29 00    JMP   ($0029)

So this will jump to $C65C (or similar,
depending on the boot slot), which will
read a sector based on the zero page
addresses set up earlier, then jump to
$0801. But $0801 got munged into an RTI
instruction. What does RTI do? It pulls
one byte off the stack and sets the
processor flags (like PLP). Then it
pulls an address off the stack and
jumps to it. Note: unlike RTS, the
address on the stack is the actual
address, not the actual address -1.

In other words, it clears all flags and
jumps to $085E.

085E-   6C 3E 00    JMP   ($003E)

More indirection. What's at $3E? At
this point, it holds the value from
$0809/$080A, which is the address
$082B.

Without a doubt, this is the strangest
way I have ever seen to read a few
sectors from track 0.

The sector read loop ends when $0806
goes negative, and the BMI at $0850
branches to $0861. So let's continue
there.

; put address $4000 in $02/$03
0861-   A9 40       LDA   #$40
0863-   85 03       STA   $03
0865-   A0 00       LDY   #$00
0867-   84 02       STY   $02

; another loop, to decrypt the 6 pages
; of boot1 code we just read
0869-   A2 06       LDX   #$06
086B-   B1 02       LDA   ($02),Y
086D-   48          PHA
086E-   4A          LSR
086F-   68          PLA
0870-   6A          ROR
0871-   91 02       STA   ($02),Y
0873-   C8          INY
0874-   D0 F5       BNE   $086B
0876-   E6 03       INC   $03
0878-   CA          DEX
0879-   D0 F0       BNE   $086B

; jump to boot1
087B-   4C CB 43    JMP   $43CB

Before I get to tracing the next stage
of the boot, I want to save my progress
with the decrypted boot0. I'll make one
more patch so that the initial JMP goes
straight to $0814 instead of the
decryption loop at $087E.





Now then. I need to interrupt the boot
at $087B and capture the decrypted
boot1 code. But of course boot0 is
still encrypted on disk, so I'll need
to do it in two callbacks.



; set up callback #1 (after boot0
; decrypts itself)
96F8-   A9 4C       LDA   #$4C
96FA-   8D 8D 08    STA   $088D
96FD-   A9 0A       LDA   #$0A
96FF-   8D 8E 08    STA   $088E
9702-   A9 97       LDA   #$97
9704-   8D 8F 08    STA   $088F

; start the boot
9707-   4C 01 08    JMP   $0801

; callback #1 is here -- set up the
; second callback after boot0 loads and
; decrypts boot1
970A-   A9 4C       LDA   #$4C
970C-   8D 7B 08    STA   $087B
970F-   A9 1C       LDA   #$1C
9711-   8D 7C 08    STA   $087C
9714-   A9 97       LDA   #$97
9716-   8D 7D 08    STA   $087D

; continue the boot
9719-   4C 14 08    JMP   $0814

; callback #2 is here -- turn off the
; slot 6 drive motor and reboot to my
; work disk
971C-   AD E8 C0    LDA   $C0E8
971F-   4C 00 C5    JMP   $C500


...reboots slot 6...
...reboots slot 5...

]BSAVE BOOT1 4000-45FF,A$4000,L$600

                   ~

               Chapter 2
    In Which We Still Haven't Found
        What We're Looking For


]CALL -151



; check for 64K
43CB-   AD 83 C0    LDA   $C083
43CE-   AD 83 C0    LDA   $C083
43D1-   A9 82       LDA   #$82
43D3-   8D 00 E0    STA   $E000
43D6-   CD 00 E0    CMP   $E000
43D9-   F0 03       BEQ   $43DE
43DB-   4C BA 45    JMP   $45BA
43DE-   A9 13       LDA   #$13
43E0-   8D 00 E0    STA   $E000
43E3-   CD 00 E0    CMP   $E000
43E6-   F0 03       BEQ   $43EB
43E8-   4C BA 45    JMP   $45BA

; zero page $DF holds the boot slot x16
43EB-   A5 DF       LDA   $DF
43ED-   8D 6A 40    STA   $406A

; set reset vector
43F0-   A9 00       LDA   #$00
43F2-   8D F2 03    STA   $03F2
43F5-   A9 08       LDA   #$08
43F7-   8D F3 03    STA   $03F3
43FA-   49 A5       EOR   #$A5
43FC-   8D F4 03    STA   $03F4

; clears hi-res screen (not shown)
43FF-   20 A8 45    JSR   $45A8

; display hi-res screen (now black)
4402-   AD 50 C0    LDA   $C050
4405-   AD 57 C0    LDA   $C057
4408-   AD 52 C0    LDA   $C052
440B-   AD 54 C0    LDA   $C054

440E-   A2 00       LDX   #$00
4410-   20 10 45    JSR   $4510



4510-   BD 00 40    LDA   $4000,X
4513-   8D 71 40    STA   $4071
4516-   BD 06 40    LDA   $4006,X
4519-   8D 72 40    STA   $4072
451C-   BD 0C 40    LDA   $400C,X
451F-   8D 6D 40    STA   $406D
4522-   BD 12 40    LDA   $4012,X
4525-   8D 6E 40    STA   $406E
4528-   BD 18 40    LDA   $4018,X
452B-   8D 1E 40    STA   $401E
452E-   20 62 42    JSR   $4262
4531-   AC 6E 40    LDY   $406E
4534-   88          DEY
4535-   10 05       BPL   $453C
4537-   A0 0F       LDY   #$0F
4539-   EE 6D 40    INC   $406D
453C-   8C 6E 40    STY   $406E
453F-   EE 72 40    INC   $4072
4542-   CE 1E 40    DEC   $401E
4545-   D0 E7       BNE   $452E
4547-   60          RTS

OK, after some further investigation,
it appears this is a multi-sector read
routine. It takes a single parameter,
in the X register, that is used to look
up all the other values from lookup
tables at $4000, $4006, $400C, $4012,
and $4018. Logical sectors are read in
descending order (DEY at $4534), while
tracks are read in ascending order. The
actual RWTS entry point is at $4262.

Here are the actual tables:



4000- 00 00 00 00 00 00



4006- 04 20 08 60 D0 D0



400C- 01 01 03 04 0A 0D



4012- 0F 0B 0B 02 02 02



4018- 04 20 18 60 30 11

$4000: low byte of storage address
$4006: high byte of storage address
$400C: starting track number
$4012: starting sector number
$4018: sector count

This is all a very compact, elegant way
to organize disk reads, but as far as I
can tell, it isn't related to the copy
protection.

Revisiting $440E with this new
understanding...

; load 4 sectors into $0400 (text page)
; starting at T01,S0F
440E-   A2 00       LDX   #$00
4410-   20 10 45    JSR   $4510

; show text page
4413-   2C 51 C0    BIT   $C051

That's the initial text-based credits
page.

; wait loop (not shown)
4416-   A2 0A       LDX   #$0A
4418-   A9 00       LDA   #$00
441A-   20 97 45    JSR   $4597
441D-   CA          DEX
441E-   D0 FA       BNE   $441A

; call the multi-sector read routine
; again, with X=#$01. So this will read
; $20 sectors into $2000 (hi-res
; graphics page) starting at T01,S0B
4420-   A2 01       LDX   #$01
4422-   20 10 45    JSR   $4510

; and show it
4425-   2C 50 C0    BIT   $C050
4428-   2C 57 C0    BIT   $C057

That's the hi-res title page with the
picture of the helicopter. My non-
working copy displayed the error
message shortly after displaying this
screen, so I'm getting close.

                   ~

               Chapter 3
    It Puts The Bits In The Basket
       Or Else It Gets The Hose


Continuing from $442B...

442B-   20 58 44    JSR   $4458



; slot number (x16)
4458-   AE 6A 40    LDX   $406A

; counters
445B-   A4 00       LDY   $00
445D-   8C 95 45    STY   $4595
4460-   A9 4A       LDA   #$4A
4462-   8C 96 45    STY   $4596

; turn on drive motor manually and go
; into read mode -- this is always
; suspicious (outside of RWTS code)
4465-   BD 89 C0    LDA   $C089,X

; look for a string of identical bits
; (waiting for the drive to settle and
; start returning real data from the
; disk)
4468-   BD 8C C0    LDA   $C08C,X
446B-   DD 8C C0    CMP   $C08C,X
446E-   D0 FB       BNE   $446B

; wait loop again
4470-   A9 FA       LDA   #$FA
4472-   20 97 45    JSR   $4597

; look for a custom prologue "CC AA 96"
4475-   BD 8C C0    LDA   $C08C,X
4478-   10 FB       BPL   $4475
447A-   CE 95 45    DEC   $4595
447D-   D0 05       BNE   $4484
447F-   CE 96 45    DEC   $4596

; if death counter hits 0, give up
4482-   F0 47       BEQ   $44CB
4484-   C9 CC       CMP   #$CC
4486-   D0 ED       BNE   $4475
4488-   BD 8C C0    LDA   $C08C,X
448B-   10 FB       BPL   $4488
448D-   C9 AA       CMP   #$AA
448F-   D0 F3       BNE   $4484
4491-   BD 8C C0    LDA   $C08C,X
4494-   10 FB       BPL   $4491
4496-   C9 96       CMP   #$96
4498-   D0 DB       BNE   $4475
449A-   8C 95 45    STY   $4595

; count nibbles until we find the
; sequence "D5 DE AA EB"
449D-   BD 8C C0    LDA   $C08C,X
44A0-   10 FB       BPL   $449D
44A2-   EE 95 45    INC   $4595
44A5-   C9 D5       CMP   #$D5
44A7-   D0 F4       BNE   $449D
44A9-   BD 8C C0    LDA   $C08C,X
44AC-   10 FB       BPL   $44A9
44AE-   C9 DE       CMP   #$DE
44B0-   D0 F7       BNE   $44A9
44B2-   BD 8C C0    LDA   $C08C,X
44B5-   10 FB       BPL   $44B2
44B7-   C9 AA       CMP   #$AA
44B9-   D0 F3       BNE   $44AE
44BB-   BD 8C C0    LDA   $C08C,X
44BE-   10 FB       BPL   $44BB
44C0-   C9 EB       CMP   #$EB
44C2-   D0 EA       BNE   $44AE
44C4-   8C 96 45    STY   $4596
44C7-   C8          INY
44C8-   D0 04       BNE   $44CE
44CA-   60          RTS

; failure path is here -- jump to The
; Badlands (prints message and hangs)
44CB-   4C 63 45    JMP   $4563

; count timing bits
44CE-   BD 8C C0    LDA   $C08C,X
44D1-   30 14       BMI   $44E7
44D3-   BD 8C C0    LDA   $C08C,X
44D6-   30 0F       BMI   $44E7
44D8-   BD 8C C0    LDA   $C08C,X
44DB-   30 16       BMI   $44F3
44DD-   BD 8C C0    LDA   $C08C,X
44E0-   30 11       BMI   $44F3
44E2-   BD 8C C0    LDA   $C08C,X
44E5-   30 0C       BMI   $44F3
44E7-   2C 96 45    BIT   $4596
44EA-   EA          NOP
44EB-   EA          NOP
44EC-   EA          NOP
44ED-   EA          NOP
44EE-   88          DEY
44EF-   F0 0D       BEQ   $44FE
44F1-   D0 DB       BNE   $44CE
44F3-   EE 96 45    INC   $4596
44F6-   EA          NOP
44F7-   EA          NOP
44F8-   EA          NOP
44F9-   EA          NOP
44FA-   F0 02       BEQ   $44FE
44FC-   D0 D0       BNE   $44CE

; check final counts of nibbles and
; bits
44FE-   AD 95 45    LDA   $4595
4501-   C9 10       CMP   #$10
4503-   B0 C6       BCS   $44CB
4505-   AD 96 45    LDA   $4596
4508-   C9 0B       CMP   #$0B
450A-   B0 BF       BCS   $44CB

; success path falls through to here --
; turn off drive motor and exit
450C-   BD 88 C0    LDA   $C088,X
450F-   60          RTS

Meanwhile, this is The Badlands (from
which there is no return):



4563-   BD 88 C0    LDA   $C088,X
4566-   20 48 45    JSR   $4548

; print error message (below)
4569-   A0 17       LDY   #$17
456B-   B9 7D 45    LDA   $457D,Y
456E-   99 B0 05    STA   $05B0,Y
4571-   88          DEY
4572-   10 F7       BPL   $456B
4574-   2C 54 C0    BIT   $C054
4577-   2C 51 C0    BIT   $C051

; 1 infinite loop
457A-   4C 7A 45    JMP   $457A

457D-  "PLEASE USE ORIGINAL DISK"

...which is exactly the behavior I saw
in my non-working copy.

                   ~

               Chapter 4
      One Byte To Rule Them All,
 And In The Encrypted Sector Bind Them


The protection check itself has no side
effects. (Hi-res page 2 is overwritten
shortly afterwards, and the counters in
$4595 and $4596 are never accessed or
saved elsewhere.) I should be able to
put an "RTS" at $4458 to neutralize it.

One small problem: the entire routine
at $4458 is encrypted on disk. Luckily
for us, the encryption is relatively
simple. It looked like this:

0869-   A2 06       LDX   #$06
086B-   B1 02       LDA   ($02),Y
086D-   48          PHA
086E-   4A          LSR
086F-   68          PLA
0870-   6A          ROR
0871-   91 02       STA   ($02),Y
0873-   C8          INY
0874-   D0 F5       BNE   $086B
0876-   E6 03       INC   $03
0878-   CA          DEX
0879-   D0 F0       BNE   $086B
087B-   4C CB 43    JMP   $43CB

This is not progressive; a byte's final
value depends only on its initial value
(not, for example, any values before or
after it, nor the page, nor byte offset
into the page). So we can change one
encrypted byte on disk without altering
any others.

But what to change it to? Well, the
value I want at $4458 is #$60, an "RTS"
instruction. There is another "RTS"
instruction at $450F. This routine is
stored on track 0 in logical sector
order:

T00,S01 => $4000
T00,S02 => $4100
T00,S03 => $4200
T00,S04 => $4300
T00,S05 => $4400
T00,S06 => $4500

So $4458 is part of T00,S05, and $450F
is part of T00,S06.

Turning to my trusty Disk Fixer sector
editor, I see that T00,S06,$0F is
#$C0. That's the encrypted value that
decrypts to #$60. I should be able to
put the same encrypted value at $4458:

T00,S05,$58: 5D -> C0

]PR#6
...works, and it is glorious...

Quod erat liberand one more thing...

                   ~

               Epilogue


In my continuing quest to automate all
the things, I compared this disk to
"Video Title Shop" (crack no. 177). It
uses the same encrypted bootloader (the
first $90 bytes of T00,S00 are
identical), but the offset of the
protection check is different. After
identifying T00,S00 as the Datasoft
encrypted bootloader, I can confidently
search for the first few (encrypted)
bytes of the protection check and
change #$5D to #$C0, wherever it starts
on track 0.

Thus, the upcoming version of Passport
will be able to auto-crack this game
and any other disks that use the same
encrypted bootloader (including "Video
Title Shop").

Here is the transcript:

                 --v--

Reading from S6,D1
T00,S00 Found Datasoft bootloader
Using built-in RWTS
Writing to S6,D2
T00,S05 Found Datasoft protection check
T00,S05,$58: 5D -> C0
Crack complete.

                 --^--

Quod erat liberandum.

                   ~

            Acknowledgments


Thanks to John Brooks for the original
disk.

---------------------------------------
A 4am crack                     No. 777
------------------EOF------------------