Two-disk replica of Prince of Persia
A recent discussion of a buggy crack for Lords of Conquest raised the issue of preserving the original bits from protected software.
So I'm exploring a different approach -- instead of patching games to bypass copy-protection code (ie: 'cracking') why not devise methods of satisfying the copy-protection checks without patching the code?
For example, the primary protection in Prince of Persia hides zero-bits between bytes on the disk, where they can't ordinarily be read or copied unless software intentionally manupulates the controller's state-machine to find it. Here's a sample from Beneath Apple ProDOS showing how the controller would ordinarily read the disk (top) and how the copy-protection detects a hidden pattern (bottom):
My own notes from that signature shows it as two columns of nibbles. The disk controller would ordinarily read the nibbles in the left-hand column, discarding the hidden 0-bits. The copy-protection routine synchronizes the controller so that it reads the nibbles in the right column, discarding the embedded 0-bits that would ordinarily be embedded in the middle of the E7
nibbles.
The hidden bits make the original signature difficult to copy, but it's possible to craft an easy-to-copy signature that satisfies the original signature check because the software merely verifies that it can extract the pattern E7 FC EE E7 FC EE EE FC
after it manipulates the controller's state-machine and receives an EE
"start" signal. In fact, it could be an ordinary data sector so long as it meets the copy-protection's requirements as follows:
- The sector starts with standard data-start
D5 AA AD
- There is a group of
E7 E7 E7
nibbles that starts within the first 255 bytes, without any prior instances of E7 - There are some throwaway nibbles to give the program sufficient time to manipulate the controller's state-machine
- There is an
EE
nibble to signal the start of the signature - Immediately after the
EE
nibble, the signature pattern followsE7 FC EE E7 FC EE EE FC
All the nibbles used in the copy-protection are valid nibbles for ordinary data, so it simply entails intentionally making data that is recorded with a pattern that will bring the controller back into normal synchronization followed by data that would ordinarily be encoded with the nibbles in the signature.
One candidate for sector data would be:
00 AC 00 AC AC AC C0 10 E0 24 88 78 BC 10 E0 24 E0 10
When that data is 6-and-2 encoded, the RWTS transforms the top 6-bits of each byte into the nibble pattern:
96 E7 E7 E7 96 96 CB F3 FC EE E7 FC EE E7 FC EE EE FC
That pattern satisfies the copy-protection routine because:
- Upon reading the
E7 E7 E7
nibbles, the copy-protection routine manipulates the controller's synchronization to force it out-of-sync - The
96 96
nibbles pass by harmlessly while the copy-protection routine is adjusting controller's synchronization - The nibble pattern
CB F3 FC
contains a bit-pattern 11001011 11110011 11111100 that forces the controller back into normal synchronization - The rest of the data directly encodes as the
EE
start signal and theE7 FC EE E7 FC EE EE FC
signature pattern
The result is a sector that's copyable, error-free, and still satisfies the copy-protection's signatgure check.
BONUS: because the sector is error-free, the rest of the sector is available to store data -- so long as we ensure that its 6-and-2 encoding doesn't accidentally store any E7
nibbles prior to the impersonated signature. (The copy-protection routine is sensitive to that!) In this instance, I used the extra space to store a patch to fix a bug in Prince of Persia's KEYS routine, which affects gameplay when played on the keyboard on an Apple //e or //c. And I modified 6 bits of the boot sector to make it load the bonus sector and call the patch routine.
Attachment | Size |
---|---|
Prince of Persia side A with KEYS bugfix and copyable-signature | 227.5 KB |
Prince of Persia side B with KEYS bugfix and copyable-signature | 227.5 KB |
- S.Elliott's blog
- Log in or register to post comments
Comments
Key patch?
Could you please explain more about the keyboard patch. What is the problem, what does the patch do?
Check the video
Ok, I will publish more details eventually -- but here's a quick summary.
It's hard to explain how badly the bug affects game play, so here's a YouTube video that demonstrates the bug...occurring unpredictably several times! https://youtu.be/eZEiDViovb4
Thanks for the explanation
That is a huge problem. I wonder why play testing didn't pick that up. Ok, thanks for your explanation and video demo. Very clear what is going on. I will ensure a patch goes into Total Replay to fix this issue.
Very interesting!
That's a really interesting write-up of how to solve the copy protection issue. Thanks! Good to see a solution which preserves the original code, but does not require special hardware to recreate actual disks.
Someone knitpicking could argue that, while the "original bits of the code are now preserved", the solution still does not preserve the bits of the disk. So there still was a theoretical possibility that the game somehow noticed the patch (maybe due to slightly different timing when verifying the signature). But it's obvious that this approach is on a much better level than patching the code itself (and any risks of remaining issues are much lower). And concerning "preserving the bits": we still have the woz images (which at least work for disk emulators, though they are not easily rewritten to an actual disk).
roughana wrote:That is a huge
I'm sure play testing did pick up on it. It was a widely known problem at the time, but we thought it was an unavoidable hardware problem so I built a circuit using a 7402 NOR gate and two 40-pin sockets to gate the AKD signal against the KEYSTROBE signal.
IMG_0885.JPG
It was only after building this circuit that I realized its logic could be adapted into the software. My college roommate had an Apple IIc, which didn't have room for a circuit like that, so I made a pre-boot disk (named "PoPABoL") that hooked the boot sector, then hooked RWTS18, then patched the keyboard routine while it loaded...all without disturbing the copy-protection.
If Broderbund Play testing
If Broderbund Play testing had picked it up, then they should have fixed it before release. Or released another version later that fixed it.
Your dedication is admirable.
Do you have source code for the fix you created?
I think they knew about the bug, but didn't know how to fix it
I still contend they discovered it during play testing but couldn't fix it for Apple //e and //c.
Youtube DRAFT mode.png
Here's where the bug actually arises, line 164-166 of the KEYS routine. When the game detects that the player has released all keys, it must not execute those two instructions in the red box because the first instruction "
lda $C010
" can pre-emptively clear the keyboard strobe if a new key is being debounced at that time, and the second instruction "sta keydown" erases the previous keyboard state required for the test in the KREAD routine.PoP Keys routine.png
PS: The bug does not occur if you replace the GI keyboard encoder with the recently-developed JCM-1 Universal Keyboard Encoder. Its designer said he intentionally debounced the AKD signal because it was a sensible engineering choice. Too bad GI didn't get that memo!
Thanks for the further detail
Thanks for the further detail. So am I inferring correctly that your suggested patch is to NOP out the two instructions in red?
Not so easy as that
Oopsie, no, not quite.
Those two instructions are critical to game play because they enable the character to stop an action, like running or climbing. NOP'ing them would cause the kid to keep going nonstop even after the player releases the control keys...like Lode Runner does. (When playing on keyboard, the Lode Runner man just keeps endlessly running or climbing until you press some other key.)
Due to its typewriter-style keyboard IO, most Apple games sufferred from "unstoppable, runaway action" if they supported keyboard at all. That's why so many Apple games required a joystick, while Commodore and Atari games typically supported your choice of joystick or keyboard.
The ideal fix would replace those two instructions with a test of the previously-saved AKD state, and a branch to skip the LDA $C010 in the case when the previous AKD indicated all keys had been released, plus a test LDA $C000 to resume normal activity after the next KEYSTROBE. Unfortunately this "ideal" solution would have required inserting more instructions into the source code and then re-assembling the program, but I had to devise a patch/kludge that could fit into the program without inserting any more bytes.
I confess, I didn't quite get it right. But it warrants another blogpost, with code and clearer pictures.