DiscSys – XOR
The Little Protection System that Wasn’t
During 1987, an amazingly addictive game was released on the BBC B computer which was an instant hit in the schools computer room. XOR from Logotron created by Astral Software Ltd, featured two characters trapped inside a scrolling maze. The idea being you collect all the shield and escape. You swapped between two shields named Magus and Questor, and you had 2,000 moves to complete your escape otherwise you’re trapped forever. Sometimes, you might have to sacrifice one of your shields in order to escape. The mazes filled with traps which would spring in to life when triggered. It was kind of like a cross between Boulder Dash and Repton.
There was a total of Fifteen levels, completion of each would provide a letter as a reward. Complete all 15 mazes those letters formed an Anagram, solve the Anagram and send the answer to the publisher. The first to find the secret of XOR would win some kind of Prize.
Imagine my excitement when this puzzle came to the Amstrad CPC.
Today’s entry isn’t about how cool this game was and still is today, but a flaw with the Disk Protection System used.
DiscSys
DiscSys was a fairly popular Disk Copy Protection mechanism in use during the 80s Amstrad CPC, essentially it worked by created Invalid Track Data that could not be copied without using specialist tools/software.
Want to know more? The people over at CPC-Power have tirelessly documented protection systems used in games found here: https://www.cpc-power.com/index.php?page=protection&lenom=discsys
Today’s entry isn’t about fully documenting DiscSys, Though I’ll cover this off in another post. Whilst looking at various disks protected with this format I found an anomaly with one particular Game. It meant that the Disk Protection Check could fail on copied disks. This flaw could still pass and continue execution as though it was a legit copy. This meant it could be copied without ensuring the right malformed data was present.
To ensure compatibility with AMSDOS Track 0 and Track 2 would always be formatted as standard CPM Sector Formats (Sector IDs #41 -> #49) with Sector size of 2, This calculates as 2^(7+2) = 512 Bytes per Sector.
This ensured AMSDOS could read the disk for a CAT, |DIR commands. The disk would appear to the user to contain standard Files. These were BASIC encoded that labelled the Date of the Protection System Used and the |CPM Command. I’ve included some Discology screen shots of the Track and Sector information from the XOR Game Disk. Track 0 and Track 2 are standard CPM Formats and just provided for comparison and contrast.
The protection system would then implement a number of tricks, such as TRACK 1 might remain Unformatted, or contain non standard format information. Game Data and code would be stored on Tracks with a single sector of size 4KB, with the “Protected Track” containing an Invalid format. This meant that standard Amstrad CPC Disc-copy programs in CPM or UTOPIA etc would fail.
Disc Sector Info Quick Start Info
Before we continue, it’s important to know that the Amstrad CPC disks were formatted following a specific set of rules, defined by the NEC µPD76A Controller Specification. I’ve included a PDF I’ve found below. My battered copy is still in storage somewhere.
At a high level, you could utilise up to 42 tracks on the Amstrad 3″ disk (That’s right… Three Inch disk, not 3.5Inch disks). Officially AMSDOS Supported 40 tracks per disk side, however the drive head had enough tolerance to reliable access at least Track 41 and many supported seeking to Track 42. Though most commercial software that used this seemed to limit themselves to Track 41. More on this in a future post.
Each Track could have multiple sectors of information, think of it as segments of a pie chart.
Each sector contain information and the first four bytes of what interests us, in the format of (Track No, Head, Sector ID, Sector Size)
The Track number should contain the Current Physical Track, Head would always be zero since the Amstrad Disk Drive needed you to flip the disk over to access the other side. Sector ID could be any number between 0-255, and Sector Size would be limited to values 0 – 5, I will cover off Sizes 6 and 7 at a later date as they’re very special.
Sector Size in Bytes is defined as :-
- 2^( 7 + Sector Size)
Therefore Size =
- 0 = 2^(7+0) = 128 Bytes
- 1 = 2^(7+1) = 256 Bytes
- 2 = 2^(7+2) = 512 Bytes
- 3 = 2^(7+3) = 1024 Bytes
- 4 = 2^(7+4) = 2048 Bytes
- 5 = 2^(7+5) = 4096 Bytes
The physical limit of the disk meant a smidge of 6kb could be stored on a single track, but that’s another story…
Sector ID’s were defined as
- CPM #41-#49
- VENDOR #C1 – #C9
- IBM #01 – #08 (Only 8 Sectors per Track!)
All three were natively supported by the CPC. I’ve included the NEC Spec if you’re interested.
Starting the Disk
You could ignore the Files on the disk, there was no addition instructions for loading the disk, just the |CPM command. |CPM instructs AMSDOS to LOAD, TRACK 0, Sector #41 to memory Address #100 and start execution at address #100.
Why is this different to the earlier protection system’s we’ve looked at?
Previous systems used a file to load another file we could easily access via BASIC, however the CPC Doesn’t provide a NATIVE BASIC command to read sectors.
Cue the assembler… we’ll need some code to do this.
Reading Track 0 – Sector #41
We’re going to fire up ADAM and create some simple code to achieve this for us. Though many emulators today offer in-built assembly options and you can use this instead.
What’s all this then? As it happens, AMSDOS ROM contains some “Hidden RSX Commands” to perform some lower level disk functions, which can be found in the Firmware Guide.
Since these commands are single byte, RSX Format dictates that BIT 7 is set to indicate the end of the command string. This means each command is #81..#89.
The program below (slightly simplified since we’re only reading a single sector) searches for command #84 – Read Sector, and sets up the address and ROM for a FAR CALL via RST#18.
ORG #a000
start
ENT $
LD hl,readcomm ; Point to RSX Command to Search
CALL #bcd4 ; Firmware Find RSX
RET nc ; Quit if not found
LD a,c ; A = ROM Command Found
LD (readsect),hl ; Address Routine found
LD (readsect+2),a ; ROM Command Found (Needed for RST#18)
LD de,0 ; D = Drive, E = Head/Side
LD c,#41 ; C = Sector Number
LD hl,#100 ; HL = Memory Address to Load Sector
;
; Read Sector and Return
;
readit
RST #18 ; Perform RST#18
DEFW readsect ; Point to the FARCALL Table
RET ; Return Control
readcomm DEFB #84 ; READ Sector Hidden RSX Command
readsect DEFS 3,0 ; Buffer for FAR Call Address and ROM
Boot Sector Loaded
If we run the code, we get the boot sector code loaded to memory.
For expediency, I’ve disassembled and documented the complete disassembly of the game loader code, shown below. Thankfully the original Author didn’t add levels of obfuscation like decode routines, we just get straight to the game loader code itself.
In short, any Game Code, would be stored on a Single Sector of 4kb in size per Track, that sector would have an ID of 0. The loader code would simply seek to the start track and load x * 4kb sectors to a memory address, incrementing track number per read.
# 1D4 LD IY,#F00 FD21000F ; Address to load Sectors #F00
# 1D8 LD B,#09 0609 ; 9 * 4K Sectors (36864 Bytes)
# 1DA CALL #176 CD7601 ; Load Sectors from Disk to memory.
Load data from current track for 9 tracks worth of 4kb data (36Kb) at Address #F00.
Once the game code has loaded, it calls on its “Protection Check” to verify a valid original disk, by calling the routine at #1F4.
The way this protection check works is SEEKing to the malformed track, read all the Sector ID Information into memory and run a comparison.
Since the sectors are malformed, the four bytes of information per sector will be …
00,00,00,00, 01,01,01,01, 02,02,02,02, … 0F,0F,0F,0F as you can see in the screen shot above
Why is it malformed? Three sections of the Sector ID are invalid.
- Track should equal the current physical track.
- Head should always be zero, since physically the 3″ drives only had one head.
- The Sector size can’t be 2^(7+15) = 4,194,304 Bytes (4mb).
What is the protection check doing?
- Seek to the Track with the malformed Sector information (Track 18)
- Set Memory Pointer to #BF02 – Look up table for Sector ID Information
- Loop 16 Times
- Request the Track’s Next Sector ID by using the Firmware to send the NEC Command #46 00 (READ ID).
- The Sector ID information is stored at memory address #BEF4
- Copy the first four bytes from #BEF4 to Address #BF02 + (4*Loop Number)
- HL = #BF02 – Start of table of Sector IDs
- Starting at #BF02 set a loop counter for 16 tries.
- Get first byte from Address in HL
- If it’s not equal to #0F
- Decrement Loop Counter
- HL = HL + 4 (Point to next block of Sector IDs)
- Loop back
- The final stage is to compare the next three bytes also contain #F
- If the next three bytes aren’t identical then loop back to the top.
- Basically creating an infinite loop and the game code appears not to run.
We can see at Address #BF3A that indeed Four #0F bytes are in succession. The protection check will pass and the game continues.
Hang on…
The sectors listed above are clearly shown starting at #00 through the #0F, why does this table start at #01?
You’re very observant! You’re right of course, we should expect to see the first sector ID listed as 00,00,00,00. Why didn’t this happen?
Physics!
A command is issued to the drive controller to move (Seek) to Track 18. The drive has to start spinning and get up to speed, and the drive head moves to a physical location using a stepper motor. Timing for sending instructions to the FDC Controller is tight, so by the time the program instructions return to start the loop to READ ID, the physical disk has always spun by Sector 00… the next ID read will be 01 and so forth. Due to hardware characteristics, the author can not guarantee the order in which sector ID information is located and reported, hence why they check all 16 sets of found IDs as opposed to going direct to an offset.
Where’s the flaw?
What if the information found, isn’t found?
The designer intended for either the program to reset the computer or enter an infinite loop preventing the game from running.
However, as discussed earlier, there was a fundamental flaw with how this check was implemented for this game and the order of sector ID’s read into memory.
If we used a copier that corrected the Sector Size byte to 0 (128 Bytes) that would fail the final check as the ID would report #0F, #0F, #0F, #00 (Track 15, Head 15, Sector 15, Size 0). four bytes aren’t identical…
The loop always finishes if ID #0F is not found with … a Zero byte!
To illustrate the issue instead of load the sector ID’s of the malformed Track, I’ll deliberately load the Track 0 instead, since it will definitely won’t contain the protection bytes expected.
The first part of the Byte Search for #0F fails and HL lands on address #BF42, The Accumulator is already loaded with a Zero byte, the memory at address #BF42 for four bytes also equals….. Zero! Protection Check complete.
Basically for this particular instance, you could format that malformed track as normal except with the first byte of the Sector ID set to 0!
I don’t know if this was an oversight when the protection system was implemented with this specific game, because typically DiscSys would use the following :-
;
; Protection Check (Aliens Disk Code)
;
#2146 LD HL,#40 214000 ; Start of Table where Sectors Read
#2149 LD B,#10 0610 ; Loop 16 Times
#214B LD DE,#04 110400 ; Offset
#214E LD A,(HL) 7E ; Get Track Number from Sector ID (READ ID)
#214F CP #0F FE0F ; Looking for Track 15 (Fake Track ID)
#2151 JR Z,#215E 280B ; If Found Quit Loop
#2153 ADD HL,DE 19 ; Add offset of 4 (Next set of Sector IDs)
#2154 DJNZ #214E 10F8 ; Loop 16 Times
#2156 LD HL,#00 210000 ; Still not Found?
#2159 LD C,#00 0E00 ; Time to Reset the System you Pirate!
#215B CALL #BD16 CD16BD ; Reset the Amstrad (Equivalent of RST#00)
#215E LD B,#04 0604 ; Now Check that all four bytes are the same Fake Sector info
#2160 CP (HL) BE ; Check for #0F, #0F, #0F, #0F
#2161 JR NZ,#2156 20F3 ; If Not Equal, Pirated Disk
#2163 INC HL 23 ; HL = HL + 1, next byte in Sector ID Information
#2164 DJNZ #2160 10FA ; Continue to Loop
#2166 RET C9 ; Yay! original disk... maybe ;p
If it the protection system failed to find the expect track info, it would simply restart your CPC.
Mind you, that’s all a moot point, since you could simply defeat the check with a RET (#C9) instruction poked to address #213
As it turned out, even with a bad copy of the disk, the protection system didn’t work as expected, running the game anyway… well… only if you ensure that the TRACK number is set to 0 in the malformed sector.
Conclusion
DiscSys had a good run and clearly a number of publishers used this system, it defeated the standard copy programs supplied with CPM or ROM, until of course DISCOLOGY, Nirvana and other specialist disk copiers came on to the the scene.
From a hacking point of view, I appreciate the developer opted to not include obfuscated code using decoding routines to get to the loader. I don’t know if this was realising they were futile or just to save development and time to implement…
Want to follow along in ADAM?
Don’t want to take my word for it? Excellent, always good to look for your self. The code below will help you for this particular GAME to return control to your ADAM environment enabling you to disassemble and trace routines for yourself.
;
; XOR Game - DiskSys 18.9.87 - Protection Loader and Hack
; Documented by Jason "The Argonaut" Brooks 2/2/2024
;
; In ADAM, load to RAMBank, OUT &7F00,196:RUN"ADAM"
;
; Once in ADAM, change the top of memory address using
;
; m#7a00
;
; This ensures the ADAM return code is located below #8000 otherwise you'll crash.
ORG #a400
start
ENT $
DI ; Disable Interrupts
POP hl ; Get Return Address
LD (adam),hl ; Set Jump Address for Later
LD bc,#7fc0
OUT (c),c ; Switch ADAM out and Standard Memory In
LD hl,readcomm
CALL #bcd4 ; Get Address of Read Sector RSX
RET nc ; Not found, off we pop
LD a,c ; A = ROM Number (7)
LD (readsect),hl ; Address of ROM Routine
LD (readsect+2),a ; Store ROM
LD de,0 ; Track 0, Head 0
LD c,#41 ; Sector #41
LD hl,#100 ; Load to Memory Address #100
CALL readit ; Read the Sector
LD a,#c3
LD hl,control
LD (#1df),a ; Patch the loader code to return control
LD (#1e0),hl ; to us!
JP #100 ; Execute the loader code.
;
; Pass control to us.
;
control
DI ; Disable Interrupts
LD a,#c9
LD (#213),a ; Patch the Protection Load Code
LD a,18 ; A = Track to Load
CALL #1f4 ; Call routines to load dodgy sector info
EI ; Enable interrupts
LD a,1
LD bc,#1a1a
CALL #bc32 ; INK 1,26
CALL #bd19
CALL #bd19 ; Needed otherwise ADAM INK will be Purple
DI ; Swap ADAM Back into memory
ld a,#21
ld (#213),a ; Restore Byte
LD bc,#7fc4
OUT (c),c
JP 0 ; Jump back to ADAM and our control
adam EQU $-2
readit
RST #18
DEFW readsect
RET
readcomm DEFB #84
readsect DEFS 3,0
Fully Documented Loader Code for XOR
For your amusement, I’ve fully documented the disassembler of the original Disk Loader for XOR.
;
; XOR Game - DiskSys 18.9.87 - Disassembly...
; Documented by Jason "The Argonaut" Brooks 31/1/2024
;
;
; Game loads by using the |CPM Command, Loading Track 0, Sector #41
To Address #100, Amstrad is reinitialised and execution
starts at #100
;
# 100 DI F3 ; Disable Interrupts
# 101 LD BC,#FEE8 01E8FE
# 104 OUT (C),C ED49 ; Check for Multiface Presence
# 106 LD A,(#00) 3A0000
# 109 CP #F3 FEF3 ; Load first Byte (If Multiface enabled #F3 if not 01)
# 10B JR Z,#101 28F4 ; If = #F3 (Multiface) keep looping until disabled.
# 10D LD C,#FF 0EFF
# 10F LD HL,#19A 219A01
# 112 CALL #BD16 CD16BD ; Initialise Program and start at Address #19A
;
; Current Track Variable in Memory.
;
# 115 DEFB 4 04 ; Current Track to Read INIT at 4
;
; INK Colour Table Only first four bytes relevant though 16 used.
;
# 116 DEFB 00, 05, #1A, 06 ; INK 0,0:INK 1,5:INK 2,26:INK 3,6
;
; Seek to Track Pointed to in A
;
# 11A LD D,A 57 ; D = Track = A
# 11B LD E,#00 1E00 ; E = Drive = 0 = A:
# 11D RST #18 DF ; Far Call to Address data
# 11E DEFW #0121 2101 ; Pointer to FAR Call
# 120 RET ; Return
;
; Far Call Address Data (#C045 - ROM 7)
;
# 121 DEFW #C045 45C0 ; AMSDOS Jump to Seek Track
# 123 DEFB #07 07 ; ROM 7
;
; Read Sector from disk at current Track. IY = Load Address
;
# 124 CALL #C976 CD76C9 ; Switch Drive Motor ON
# 127 CALL #C947 CD47C9 ; Wait for FDC to be Ready
# 12A LD BC,#FB7E 017EFB ; Data Port of FDC Controller
# 12D LD A,#46 3E46 ; Command #46 - Read Sector
# 12F CALL #C95C CD5CC9 ; Send Command to FDC
# 132 XOR A AF ; Drive Number = 0 - Drive A
# 133 CALL #C95C CD5CC9 ; Send Command Parameter 1
# 136 LD A,(#115) 3A1501 ; Get Current Sector
# 139 CALL #C95C CD5CC9 ; Send Command Parameter 2
# 13C XOR A AF ; Head = 0
# 13D CALL #C95C CD5CC9 ; Send Command Parameter 3
# 140 XOR A AF ; Sector Number to Read (0)
# 141 CALL #C95C CD5CC9 ; Send Command Parameter 4
# 144 LD A,#05 3E05 ; Sector Size to Read, 5 = 4Kb
# 146 CALL #C95C CD5CC9 ; Send Command Parameter 5
# 149 XOR A AF ; Last Sector to read (0 )
# 14A CALL #C95C CD5CC9 ; Send Command Parameter 6
# 14D LD A,#11 3E11 ; GAP #3 Size
# 14F CALL #C95C CD5CC9 ; Send Command Parameter 7
# 152 LD A,#FF 3EFF ; Data Length if <255 bytes.
# 154 DI F3 ; Disable Interrupts for next bit
# 155 CALL #C95C CD5CC9 ; Send Command Parameter 8
# 158 PUSH IY FDE5 ;
# 15A POP HL E1 ; HL = IY - Memory Address to load data
# 15B CALL #C6E5 CDE5C6 ; FDC Execute command.
# 15E EI FB ; Re-enable Interrupts
# 15F PUSH HL E5 ; HL Updated to point to next Memory
; Address after loading sector
# 160 POP IY FDE1 ; IY = HL (Next memory load address)
# 162 CALL #184 CD8401 ; Select ROM 7
# 165 JP #C91C C31CC9 ; Read FDC Results and complete command
;
; Address #115 = Current Track to Seek to.
;
# 168 CALL #184 CD8401 ; Select ROM 7
# 16B CALL #124 CD2401 ; Read Sector at Current Track
# 16E CALL #190 CD9001 ; Restore Previous ROM
# 171 LD HL,#115 211501 ; Pointer to Current Track
# 174 INC (HL) 34 ; Increment Memory by 1
# 175 RET C9 ; Finished
;
; Load data from multiple sectors.
;
# 176 PUSH BC C5 ; Preserve Loop Counter
# 177 LD A,(#115) 3A1501 ; Get Current Track to Seek
# 17A CALL #11A CD1A01 ; Seek to Track A
# 17D CALL #168 CD6801 ; Read Sector
# 180 POP BC C1 ; Restore Counter
# 181 DJNZ #176 10F3 ; Loop until complete
# 183 RET C9 ; Return Control.
;
; Select ROM 7 and Store Previously ROM State
;
# 184 PUSH BC C5
# 185 LD C,#07 0E07
# 187 CALL #B90F CD0FB9 ; Select ROM 7
# 18A LD (#BF00),BC ED4300BF ; Store Previously Selected ROM
# 18E POP BC C1
# 18F RET C9
;
; Restore Previous ROM State stored in #BF00
;
# 190 PUSH BC C5
# 191 LD BC,(#BF00) ED4B00BF ; Get Previous ROM State
# 195 CALL #B918 CD18B9 ; And Restore It
# 198 POP BC C1
# 199 RET C9
;
; Initial Check for Multiface Complete, lets start the loader!
;
# 19A LD C,#07 0E07
# 19C CALL #BCCE CDCEBC ; Enable AMSDOS ROM
# 19F LD A,#01 3E01
# 1A1 CALL #BC0E CD0EBC ; MODE 1
# 1A4 LD BC,#00 010000
# 1A7 CALL #BC38 CD38BC ; BORDER 0
;
; Set the INKS Loop
;
# 1AA LD HL,#116 211601 ; Pointer to Table of Colours
# 1AD XOR A AF ; A = 0 (INK 0)
# 1AE LD C,(HL) 4E ; Load C with contents of HL (INK Colour)
# 1AF INC HL 23 ; HL = HL + 1
# 1B0 PUSH HL E5
# 1B1 PUSH AF F5 ; Preserve AF and HL, our INK Number and Pointer
# 1B2 LD B,C 41 ; B = C (Amstrad Supports Flashing Colours - Ensures no flashing)
# 1B3 CALL #BC32 CD32BC ; INK A,B,C
# 1B6 POP AF F1
# 1B7 POP HL E1 ; Restore Registers
# 1B8 INC A 3C ; A = A + 1
# 1B9 CP #10 FE10 ; Has A reached 16 yet?
# 1BB JR NZ,#1AE 20F1 ; Loop Back until all inks are Set
;
; Load first section of code.
;
# 1BD LD B,#05 0605 ; Five Sectors to load
# 1BF LD IY,#4000 FD210040 ; Load Address #4000
# 1C3 CALL #176 CD7601 ; Call Disk Routine to load sectors to memory
;
; Each sector is 4kb in length. 5 sectors mean 20kb was loaded to Address
; #4000-#8FFF
;
; Some code needs relocating from #8000 to #A000 so we'll move it.
;
# 1C6 LD HL,#8000 210080 ; Start address of Code to Move
# 1C9 LD DE,#A000 1100A0 ; Destination Address of Code to move
# 1CC LD BC,#240 014002 ; Length of #240
# 1CF LDIR EDB0 ; COPY Code
# 1D1 CALL #A000 CD00A0 ; Call code to load title screen
;
; Title page dealt with, now the game code.
;
# 1D4 LD IY,#F00 FD21000F ; Address to load Sectors #F00
# 1D8 LD B,#09 0609 ; 9 * 4K Sectors (36864 Bytes)
# 1DA CALL #176 CD7601 ; Load Sectors from Disk to memory.
;
;
;
# 1DD LD A,#12 3E12 ; Track 18
# 1DF CALL #1F4 CDF401 ; Call the Protection System Check
;
; Stop the drive from Spinning
;
# 1E2 LD BC,#FA7E 017EFA ; Drive Motor
# 1E5 XOR A AF ; A = 0 (To Switch Motor Off)
# 1E6 OUT (C),A ED79 ; Switch the Drive Motor Off
;
;
;
# 1E8 DI F3
# 1E9 LD BC,#7F8D 018D7F
# 1EC OUT (C),C ED49 ; Disable Upper/Lower ROM
;
;
;
# 1EE CALL #A000 CD00A0 ; Call Another Routine
# 1F1 JP #1000 C30010 ; Start the Game Code.
;
; DiskSys Protection System Check
;
# 1F4 CALL #11A CD1A01 ; Seek to Track Pointed to in A
# 1F7 LD DE,#BF02 1102BF ; Start of Destination Buffer to
; Copy Sector ID information
# 1FA LD B,#10 0610 ; Loop Counter = 16
# 1FC PUSH BC C5
# 1FD PUSH DE D5 ; Preserve BC and DE Registers.
# 1FE CALL #184 CD8401 ; Select Upper ROM 7
# 201 CALL #22C CD2C02 ; Send READ ID to FDC Controller
# 204 CALL #190 CD9001 ; Restore ROM to previous states
# 207 POP DE D1 ; Restore DE (Destination to copy)
# 208 LD HL,#BE4F 214FBE ; Firmware Memory Block containing
; Sector ID information
# 20B LD BC,#04 010400
# 20E LDIR EDB0 ; Copy these four bytes to DE
# 210 POP BC C1
# 211 DJNZ #1FC 10E9 ; Restore BC and loop four times..
;
; Now Sector ID's are loaded into memory, DISKSYS Wants to check their presence.
; it is expecting the memory to be populated with :-
; 0,0,0,0, 1,1,1,1, 2,2,2,2 ... f,f,f,f
; Though the order could change depending when the READ ID command started in
; relation to the disk spinning in the drive
;
# 213 LD HL,#BF02 2102BF ; Start of Sector ID Buffer Copied Above
# 216 LD B,#10 0610 ; Loop 16 times
# 218 LD DE,#04 110400 ; An Offset of Four (Four bytes copied above)
# 21B LD A,(HL) 7E ; A = Contents of memory at HL
# 21C CP #0F FE0F ; Does it contain #F?
# 21E JR Z,#223 2803 ; Found our interesting sector ID
; Part 1 Check complete.
# 220 ADD HL,DE 19 ; otherwise add 4 to HL to point to
# 221 DJNZ #21B 10F8 ; The next block of Sectors and loop
;
; Secondary Check, make sure that the sector ID's all contain the same digits
; Some copiers would correct the fourth byte (Size) to be 0 meaning 128 Bytes
;
# 223 LD B,#04 0604 ; Loop Counter
# 225 CP (HL) BE ; A is already primed Compare with
; contents of HL
# 226 JR NZ,#213 20EB ; If Not Equal Jump to an infinite loop...
# 228 INC HL 23 ; HL = HL + 1
# 229 DJNZ #225 10FA ; Loop until B = 0
# 22B RET C9 ; Check Complete, all ok!
;
; Simple routine to call AMSDOS Rom to send the NED µPD765A Controller
; The READ ID Command. Basically scans the track for sector information.
;
; DiskSYS Works by having a track or more with malformed corrupt sector ID's
; By scanning the track and reading the Sector ID Information
; It works out if the corrupt information is available
; Since Copiers weren't meant to be able to copy this information.
;
# 22C CALL #C976 CD76C9 ; Drive Motor ON
# 22F CALL #C947 CD47C9 ; Wait for DDFCDC to Be Ready
# 232 LD BC,#FB7E 017EFB ; Command Port
# 235 LD A,#4A 3E4A ; SEEK Command
# 237 CALL #C95C CD5CC9 ; Send Command to FDC Controller
# 23A XOR A AF ; Drive 0 Parameter
# 23B CALL #C95C CD5CC9 ; Send Command to FDC Controller
# 23E JP #C91C C31CC9 ; Get FDC Results