Book Read Free

PoC or GTFO, Volume 2

Page 13

by Manul Laphroaig


  Wavy Navy uses ten sectors per track, while Olympic Decathlon uses eleven and Karateka uses a dozen. The sectors in these examples are all the regular size, but encoded in a wasteful manner. (Primarily the 4-and-4 encoding was used because the decoder is very small, but sometimes 5-and-3 because the decoder looks weird when compared with the more familiar 6-and-2 encoding.) The wasteful encoding is the reason for the reduced sector count; there really isn’t more room for more sectors.

  More sectors

  The standard DOS 3.3 format disk uses 16 individual sectors per track, with relatively large gaps between the sectors. Consider how much space would be available if those sectors were combined into a single large sector, with a single field that combines both address (specifically, only the track number) and data fields. Yes, it would require reading the entire track in order to find the field again once the track had been verified, but for some applications, performance is not that critical. This is what Infocom did, on programs such as A Mind Forever Voyaging. Once the track had been found, and the data field found again, then the program read (and discarded) sectors sequentially until the required one was found. Again, if the performance is not that critical, the fact that the routine can fetch only one sector at a time is not an issue. In fact, the implementation works well enough for the text-adventure scenario in which it was used. Since the user will be reading the text while additional text is loading, the time required for that loading goes mostly unnoticed.

  Consider how much space would be available if those gaps were reduced to the minimum of five self-synchronizing values before the address field prologue, with just a few bytes of gap between the address and data headers. Then reducing the prologue byte count from three to two, and the epilogue byte count from two to one. Consider how much space would be available by merging groups of sectors. If you converted the track into six sectors of three times the size, you would have RWTS18. This is a good compromise between speed and density. On one side, having fewer sectors means less processing; and on the other side, having more sectors means less latency to find a sector. The RWTS18 routine also supports “read scattering” by assigning a dummy write address to the pages that aren’t needed.

  This second technique was used very heavily by Brøderbund, on programs such as Airheart (and even three years later, on Prince of Persia), but other companies made use of it, too, such as Infogrames in Hold-Up. Interestingly, in the case of Airheart, after compressing the title screen to reduce its size on the disk, the rest of the game fit on a regular 16-sector disk.

  Big sectors

  There is no requirement to define multiple sectors per track. It is possible to define a single sector that spans the entire track.44 However, there can be a significant time penalty while reading such a track, because it requires up to one complete rotation in order to find the start of the sector.

  Lady Tut uses a single sector per track, at a size equivalent to eleven 256-byte sectors.

  Encoded sectors

  As noted previously, there is no reason for a disk to use our sixth table—there is no reason to have the nibbilisation entries in that order, nor even to use those values at all. Any alteration to the table results in a disk that can be copied freely, but whose contents cannot be read from the outside. Further, the DOS on such a disk cannot write files from the inside to the outside. The reason why the read would fail is because the standard table would be applied to data that requires the alternative table to decode, resulting in the wrong decoding. The reason why the write would fail is because the alternative table would be applied to data that requires the standard table to encode, resulting in the wrong encoding.

  Figure 10.17: Floppy sectors interleaving.

  Maze Craze Construction Set uses an alternative nibble table—all of the values from #$A9-FF from our first table. These values might have been chosen because they provide the least sparse array when used as indexes.

  Bop’N Wrestle uses the regular nibble table and a standard DOS 3.3, but in reverse order.

  Duplicated sectors

  The address field carries the sector number, but the controller does not need or use this information, except when the boot PROM is requested to read a sector. Therefore, it is possible to have multiple sectors with the same number.45 There are numerous ways in which they could be distinguished, such as by the volume number. A protection technique could set every sector number to the same value in the address field. It could set them all to zero, provided that the checksum algorithm is changed, so that the boot PROM will read successfully only the true sector zero, in order to boot the disk. It could also use the volume number from the address field as the page number in which to write the sector data. This would be a very compact way to load data without the need to pass the address as a parameter to the loader.

  Math Blaster has two sectors numbered zero on track zero. The program distinguishes between them by examining the first nibble after the address field epilogue, but the checksum of the second sector zero also fails verification, which is why the boot PROM does not see it. This protection cannot be reproduced by a sector-copier or track-copier, because those copiers will write only a single sector zero to a track. It is unpredictable which of the two sector zeroes would be written, but even if the true one is chosen, the copy is revealed by the program missing the duplicated sector.

  Sector numbering

  The address field carries the sector number, but the controller does not need or use this information, except when the boot PROM is requested to read a sector. Therefore, it is possible to have sectors whose number is not in the range of zero to fifteen.46 Any eight-bit value can be used, as long as the program is expecting it. This protection cannot be reproduced by a sector-copier, because the copier will not copy those sectors at all.

  Sector location

  The address field carries the track and sector number, but the controller does not need or use this information, except when the boot PROM is requested to read a sector. Therefore, it is possible for a sector to “lie” about its location on the disk. For example, the address field of sector three on track zero could label itself as sector zero on track three. This protection cannot be reproduced by a sector-copier which relies on DOS to perform the write, because they will not duplicate this information, because DOS will fill in the address field by itself when placing the sector on the disk. Thus, a program that seeks to a track that contains “misplaced” sectors will not find any misplaced sectors, or will receive the wrong content instead.

  Discover uses this technique; it changes the identity of one particular sector in the sector interleave table, on one particular track.

  Synchronised sectors

  Since the approximate rotation speed of the drive is known to be roughly 300 RPM, it becomes possible to place sectors at specific locations on a track, such that they have a special position relative to other sectors on the same track. This is difficult to reproduce because of the delay that is introduced while a sector-copier is writing the data.

  Hard Hat Mack takes this to the extreme, by requiring that one track has all 16 sectors in incremental order. This protection is highly unlikely to be reproduced by using a sector-copier, because after factoring in the rotation speed of the drive, the next sector is more likely to be placed halfway around the disk.

  Bad sectors

  Some protections rely on the fact that intentionally bad sectors should return a read error. For example, checksum mismatch in the simplest case, but potentially physical damage could be used, too.

  Drelbs uses this technique. This protection cannot be reproduced even with a bit-copier, because the copy will have no sectors that cannot be read.

  Dead-space bytes

  The data for a sector is well defined, but apart from the optional presence of the self-synchronizing values, the data between sectors is not defined at all. As a result, it is not often copied, either. It is possible to place specific counts of specific values in this location, which can be checked later. A prog
ram can detect a copy by the absence or wrong count of the special values.

  Randamn checks the value of the byte immediately before the prologue of a particular sector, and reboots if the value looks like a self-synchronizing value. (A bit-copier might insert this values when asked to match the track length, and a sector-copier would always insert the value.)

  Binomial Multiplication counts the number of values that appear between the address field epilogue and the data field prologue, and between the data field epilogue and the next sector address field prologue, for all of the sectors on a particular track. This protection cannot be reproduced by a sector-copier or a track-copier, because those copiers will discard the original data between the sectors.

  Timing bits

  The Disk ][ controller shifts in bits at a rate equivalent to one bit every four CPU cycles, once the first one-bit is seen. Thus, a full nibble takes the equivalent of 32 CPU cycles to shift in. After the full nibble is shifted in, the controller holds it in the QA switch of the Data Register for the equivalent of another four CPU cycles, to allow it to be fetched reliably. Those four CPU cycles elapse, and once a one-bit is seen, the QA switch of the Data Register will be zeroed, and then the controller will begin to shift in more bits. The significant part of that statement is “once a one-bit is seen.” It is possible to intentionally introduce “timing” (zero) bits into the stream in order to delay the reset. For each zero-bit that is present, the previous value will be held for another eight CPU cycles. For code that is not expecting these zero-bits to be present, a nibble that is being held back will be indistinguishable from a nibble that has newly arrived.

  Creation uses this technique.

  ;wait for nibble to arrive

  B94F LDA $C08C,X

  B952 BPL $B94F

  ;watch for #$D5

  B954 CMP #$D5

  B956 BNE $B948

  ;delay to ensure > 4 cycles

  ;before the next read occurs

  B958 NOP

  ;read data latch

  B959 LDA $C08C,X

  ;Check if nibble has changed.

  ;If zero-bit is present,

  ;then read value lasts longer

  B95C CMP #$D5

  B95E BEQ $B972

  Hacker II requires a pattern of zero-bits in the stream. The effect of the delayed shift becomes clear when we count cycles.

  ;initialise mask

  403A LDA #$08

  ...

  ;wait for nibble to arrive

  4044 LDY $C08C,X

  4047 BPL $4044 ;2 cycles

  ;watch for #$FB

  4049 CPY #$FB ;2 cycles

  404B BNE $403A ;2 cycles

  ;not a do-nothing instruction!

  ;exists to be timing-identical

  ;to the BEQ at $4062

  404D BEQ $404F ;3 cycles

  404F NOP ;2 cycles

  4050 NOP ;2 cycles

  ;read data latch

  4051 LDY $C08C,X;4 cycles

  ;check how many bits have

  ;shifted in

  4054 CPY #$08

  ;shift carry into A

  4056 ROL

  ;until set bit is shifted out

  ;(takes five rounds)

  4057 BCS $4064

  ;wait for nibble to arrive

  4059 LDY $C08C,X

  405C BPL $4059 ;2 cycles

  ;watch for #$FF

  405E CPY #$FF ;2 cycles

  4060 BNE $403A ;2 cycles

  4062 BEQ $404F ;3 cycles

  ;wait for nibble to arrive

  4064 LDY $C08C,X

  4067 BPL $4064

  ;remember its value

  4069 STY $07

  ;check if pattern was seen

  ;(alternating zero-bit)

  406B CMP #$0A

  406D BNE $403A

  ;wait for nibble to arrive

  406F LDA $C08C,X

  4072 BPL $406F

  ;checksum against previous

  ;value must both be #$FF

  4074 SEC

  4075 ROL

  4076 AND $07

  4078 EOR #$FF

  407A BEQ $4080

  The timing loop is long enough for four nibbles to be shifted in if no zero-bit is present, resulting in a value of at least #$08. (Specifically the right-hand "F" from the value "FF".) If a zero-bit is present, then fewer than four nibbles will be shifted in, resulting in a value of less than #$08. This explains the "CPY #$08" instruction at $4054. It is checking if a one-bit has been shifted in four times or three times.

  The "CMP #$0A" instruction at $406B is checking the final results of the multiple CPYs that were made. In binary, the results look like 01010 but prior to that, the results progress like this:

  00010000

  00100001

  01000010

  10000101

  00001010

  That means it is expecting the first pass to have a value of less than eight (carry clear), then a value of at least eight (carry set), then a value of less than eight (carry clear), then a value of at least eight (carry set), and finally a value of less than eight (carry clear), followed by two "FF"s. That requires the stream to look like FB 0 FF FF 0 FF FF 0 Fx FF FF.

  Floating bits

  What happens if more than two consecutive zero-bits are present in a stream? Something random. The Automatic Gain Control circuit will eventually insert a one-bit because of amplified noise. It might happen immediately after the second zero-bit, or it might happen after several more zero-bits. The point is that reading that part of the stream repeatedly will yield different responses.

  Mr. Do! uses this technique.

  ;set counter to be used later

  0710 LDY #$06

  ...

  ;set state

  0713 LDA #$FF

  0715 STA $07C2

  ;wait for nibble to arrive

  0718 LDA $C088,X

  071B BPL $0718

  ;watch for #$D5

  071D CMP #$D5

  071F BNE $0718

  ;wait for nibble to arrive

  0721 LDA $C088,X

  0724 BPL $0721

  ;watch for #$9B

  0726 CMP #$9B

  0728 BNE $071D

  ;wait for nibble to arrive

  072A LDA $C088,X

  072D BPL $072A

  ;watch for #$AB

  072F CMP #$AB

  0731 BNE $071D

  ;wait for nibble to arrive

  0733 LDA $C088,X

  7036 BPL $0733

  ;watch for #$B2

  0738 CMP #$B2

  073A BNE $071D

  ;wait for nibble to arrive

  073C LDA $C088,X

  073F BPL $073C

  ;watch for #$9E

  0741 CMP #$9E

  0743 BNE $071D

  ;wait for nibble to arrive

  074E LDA $C088,X

  0751 BPL $074E

  ;loop six times

  0753 DEY

  0754 BNE $074E

  ;change state

  0756 INC $07C2

  0759 BNE $2761

  ;store last read value

  ;on first pass

  075B STA $07C3

  ;allow complete revolution

  ;and read again

  075E JMP $071D

  ;Check last read value on

  ;subsequent pass. Must be

  ;different from the first pass

  0761 CMP $07C3

  0764 BNE $0771

  ;retry up to four times

  0766 INC $07C2

  0769 LDA $07C2

  076C CMP #$08

  076E BNE $271D

  On the first pass, the program watches for the sequence $#D5 #$9B #$AB #$B2 #$9E #$BE, skips the next five nibbles, and then reads and saves the sixth nibble. On subsequent passes, the program watches again for the sequence $#D5 #$9B #$AB #$B2 #$9E #$BE, skips the next five nibbles, and then reads and compares the sixth nibble against the sixth nibble that was read initially. The value that is read will always be a legal value, but on the original disk,
with multiple zero-bits in the stream, the value that was read in one of the subsequent passes will not match the value that was read in the first pass. No matter how many extra zero-bits existed in the stream, the bit-copier will not write them out. Instead, it will “freeze” the appearance of the stream, and normalise it so that there are no more than two zero-bits emitted. As a result, the sixth nibble that was read will have the same value for all passes, and therefore fail the protection check.

  Nibble count

  Since a track is simply a stream of bits, it is possible to control the layout of the values in that stream, as long as it follows the rules of the hardware. The number of self-sychronizing values can be reduced to a single set of the minimum number, if performance is not a consideration. That means there are no other zero-bits present on the track. However, a bit-copier cannot detect the zero-bits reliably (neither their presence, nor their number), so it is left to guess if the value #$FF must be stored using eight or ten bits. (That is, if it is a data nibble or a self-synchronizing value.) If there are enough #$FF bytes on a track, and if the bit-copier assumes that every one of them must be ten bits wide, then it is possible that the bit-copier will write more data than can fit on the track, resulting in part of the track being overwritten when the revolution completes before the write completes.

  As a separate technique, it is also possible to reduce the speed of the drive while writing the data to the original disk, resulting in a track that is so dense, that the data cannot fit on a disk when written at regular speed. This is known as a “fat” track.

  The more common technique is to simply use a sequence of nibbles with enough zero-bits between them, that the “delayed fetch” effect is triggered. (§10:7.2.) When the zero-bits are present, and if the fetch is fast enough,47 then there will appear to be more nibbles of a particular value than really exist, because the next bit will not be ready to shift in. A program that counts the number of nibbles will see more nibbles in the copy than in the original.

 

‹ Prev