Protection for the uninitiated
One of the most interesting features of a commercial arcade game these days, from an ARM coder's point of view, is the way in which the disc protection is implemented. For it to be successful, we need to write and hide a section of code which evades any attempts at reverse-assembly while still being able to keep compatibility with future operating systems. What we won't be doing is direct hardware access due to the variety of disc controllers used in Acorn's machines.
Floppy disc layout
Since most 'protected' discs can easily be copied
by a sector copier such as Investigator or the legendary Xcopy (Amiga),
I'll only concern myself with a disc format that won't be copied by the
usual RISC OS *Copy or *Backup commands. So, as far as we're concerned,
a floppy disc is laid out by the Filecore module to be a continuous 800k
stream of data; the details of tracks, sectors and heads need not bother
us. For the user's convenience, there is an area on the disc called
the map, which indexes where each file is on the disc. When you write
a file to the disc normally, RISC OS looks for some free space in the map,
marks this free space as 'used' and writes the data into the blank area.
So, if we write some data onto the disc with ADFS_DiscOp (see box-out),
without updating the map appropriately, we have data which looks like free
space on the disc but is in fact essential to the running of the game,
and which won't be copied by *Copy or *Backup.
Cracking the code
To play with it, run the !MakeDisc obey file on the cover disc with a blank, double-density disc in the floppy drive (or one you don't mind being wiped). Then open it up from the Filer and run the example game, !Shoddy. Now examine the disc; all you'll find is the !Shoddy folder and a short, garbled piece of code as the !Run file. What's actually on the disc is shown in the diagram; the job of the !Run file is to fetch and execute the more involved 'game code' off the disc. Notice the use of OS_GetEnv to read the 'upper RAM limit'; as well as being used to find out whether we've got enough memory to play with, we can use the top 8k as a stack for our program by simply subtracting 8192 from the pointer passed in R1 (the RAM limit) and using this as our R13.
The other preventative measure to stop hackers in
the !Run file is to encrypt the code which executes the next piece of code
off the disc. We now have three pieces of code to think about:
Our other anti-hacker tactic is to set OS_CallAfter to call a piece of code which crashes the machine, after 20cs. This is so that if the decryption code takes longer than expected to execute (that is to say, if some logging or hacking software is watching what is going on), the machine will simply crash. A time of 150cs is sufficient to stop any debugger before it can present its findings to the potential hacker.
How do they do that?
That's the protection from a hacker's point of view; writing this sort of system to a disc isn't as precarious as it sounds.
Each time you make a copy of the game with !MakeDisc,
you'll find it is encoded with a serial number, which is simply a random
word generated from the current time and the BASIC RND function.
This is used as a seed for our random number generator, which is used in
turn to generate successive 'key' bytes to encode the sound module and
loading screen. The key is converted to a readable representation
and used as the title of the disc; this doesn't give away much about the
encryption and serves as an example of how to implement a quotable 'serial
number' for the customer; obviously if you were to use this as a serial
number, random numbers would need to be substituted for something a little
more ordered to avoid duplication. The game code then reads and interprets
the title of the disc to find the seed to decode its resources.
|Garbage in, garbage out
EOR is an instruction commonly used for simple 'encryption' purposes, since it has the pleasing property that any number EORed with another produces garbage, but when EORed with the same number again gives the input number back again. It works on a bit level like this:
0 EOR 0 = 0
The game code also shows how to 'hide' modules and execute them from memory rather than *RMLoading them. Once we've loaded the sound effect module into memory, OS_Module 10 is called to link it to the module chain, and we can call the SPlay_Howl command to hear the noise.
Chinks in the armour
The system is a long way from perfect. Most protected discs use a non-standard disc format, or leave some tracks unformatted, or feign defects where there is useful data. However clever the disc format, they can nearly all be foiled by a suitable copying program, which is why I've ignored this problem. As for hacking the disc protection off (a more 'satisfactory' solution from a hacker's point of view), our system makes it quite inconvenient seeing as no actual files are dealt with, only data. I've seen a way to get around every detail of this protection scheme, just by explaining it, but remember that disc protection will always be hacked by somebody and a line has to be drawn. A hypothetical 'perfect' hacking tool would be able to record a sequence of ADFS_DiscOps when the game loaded the first time, then intercept them and 'play back' the correct responses to the program.
The best way of looking at this system is as a curiosity with some useful ARM coding tricks in. Protecting copyright like this will always cause hassle for the user and will eventually cause incompatibility with future operating systems, and disc protection by its very nature cannot be made entirely 'legal' (as far as as Acorn are concerned) without being ineffectual.
|Useful SWIs used this month
Entry: R1 = 1 to read, 2 to write
(this is by no means a full documentation; the StrongHelp manual and PRMs are the best resort here)
Entry: R0 = number base (anything from 2-36; 16, hex, used here)