Skip to content

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.

XOR – Logotron by Astral Software

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.

Track 0 – CPM Format Track – AMSDOS Standard
Track 2 – CPM Format Track

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.

Game Code Track – 2^(7+5) = 4096 Bytes per Sector
Discology Track Report – DiscSys Protected Track

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.

Typical Amstrad CPC 3″ Disk – Yes I’m the original Author of Choice Cheats.

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.

Example BASIC File Loader

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.
What the protection system expects to see in memory, SECTOR IDs for Malformed Track

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

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.