Your Spectrum
Issue 8, October 1984 - Running Repairs
Home Contents KwikPik


This article must be read in conjunction with "Patching Up The 'Drives" in issue 14.

  R U N N I N G
R E P A I R S
 
   
Hands up those who've lost files on a Microdrive cartridge. Well, for goodness sake, don't throw it away because Andrew Pennell's beginning to solve the problem. Presented here is a way of examining and printing up the suspect sectors ... and there's more to come.


 
 
Good as the ZX Microdrives are, like all forms of media, they're not perfect. Every once in a while, faults occur that result in a "File not found" message and, of course, Murphy's Law determines that this only happens to those that have not been backed-up.
The program I'm presenting is designed to enable 'repairs' to be made to corrupted and unloadable files. It's in two parts. The first (presented here) allows you to examine the cartridge for faults, and print out all damaged and suspect sectors. The second, to be included in a forthcoming issue, will allow individual sectors to be read in - even if faulty - corrected, then written out, so you can still recover the file. It won't be perfect, because badly corrupted files can be impossible to fix; however, it'll work for many.

SAVE YOUR SECTORS

Before delving into the program, let's examine first why sectors become unreadable. Usually, it's due to some mechanical or magnetic abuse that results in some part of the tape losing bits of data. Thus, when the Spectrum tries to read the affected sector, the data is altered and the checksum saved with it no longer matches - so loading fails to take place. What our section of machine code does is scan the cartridge, reading each sector (whether corrupted or not) and storing its particulars in a Basic array, z$. Given the sorted array, the Basic part then uses the information to calculate which sectors are damaged or missing altogether; the second stage uses this information to allow access to individual sectors, in order to re-create them.
Our first move will be to enter the 500-odds bytes of machine code. Those without an assembler will have to use the Hex loader given; enter the code correctly, then save it on to cartridge with:
SAVE *"m";1;"SL.CODE" CODE 30000,500
Next, enter the main program, and save it with:
SAVE *"m";1;"repair" LINE 9000

Note that line 130 will only be accepted with the machine code entered, and activated by RAND USR 30000.

CODE ANALYSIS

The code works by adding a command '*L' which scans a given cartridge, storing its details in the array z$(200,13); then it sorts the data using a bubble sort. NEWVEC is the additional syntax checker, which okays the statement, gets the 'drive number, alters it to suit the ROM, then does the actual work. Routine WATROM is similar to the one detailed in All Change (see the August issue), altering the CALLs in the program to suit whichever shadow ROM is in place. FIND is the main entry point. It starts by creating an 'm' area in CHANS, and putting the motor on; each sector is read in, and its checksum calculated to see if it has corrupted. The ROM checksum routine cannot be used as it alters the checksum byte - which makes it impractical for part two. If the sector is used, its name, record number and sector number are stored in z$, along with a flag that shows if it's an EOF sector - and whether it's corrupted or not. The code at NEXT ensures the whole cartridge has been read, before closing the 'm' channel. The border is made green, and the sort routine entered.
SORT is a not very amazing bubble- sort routine. It sorts the elements of z$ into order, using the crudest sort of algorithm possible. I chose it for simplicity, not speed - though it is, of course, many times faster than anything in Basic. The routine can take up to a minute to sort a full cartridge; those feeling nervous are allowed to break into it while it sorts.
Routine NXHDBF, the most important
one of all gets down to the business of scanning the tape, doing its checksum, and seeing if it's used or not. CHKSUM is basically the same as the one in the ROM, but with an instruction at the end removed. Finally, FINDZ$ is responsible for searching the variables area for the array z$, and finding the location of the first element. Note that no checks are made on the dimensions or size of the array, only its existence. If z$ is not the proper size, then Basic may crash - so beware.

ROUTINE ACTION

The business of examining the sector data is carried out in Basic - because it's easier to change, and speed is not relevant. After the '*L', each element of z$ contains 13 bytes of data: bytes one to 10 are the file-name, byte 11 the record number, byte 12 the sector, and byte 13 the flag. Option 1 prints all the file names, like CAT but including CHR$ 0 file names. While using it, you may get strange file names at the top of the catalogue; don't worry - all cartridges have a couple of strangely-named sectors on them (as a by-product of the FORMAT routine) all starting with CHR$ 0. Option 2 prints a sector list, which consists of each used sector, its file name record number, sector number, and type. From this, you can work out what's missing from it, as record numbers should rise from zero up to one with EOF against it. It also tells you if any are corrupted, though you don't have to scan lines of information to find the faults; Option 3 prints all the corrupted sectors, while Option 4 will examine all the sectors of a given file and tell you if any are missing or corrupted. As it's in Basic, you can change it to suit your needs.
All this allows you to find the faults in your cartridges; watch out for part two where you'll discover how to fix them.


This program is available on "ZIPi'T'ape".

100 RESTORE 1000: CLEAR 29999
105 LET s=0
110 FOR i=30000 TO 30465
120 READ a: POKE i,a: LET s=s+a
130 NEXT i
140 IF s<>51998 THEN PRINT "Data error": STOP
150 PRINT "Data OK"
1000 DATA 33,58,117,34,183,92
1010 DATA 1,0,0,201,198,206
1020 DATA 254,42,194,240,1,215
1030 DATA 32,0,246,32,254,108
1040 DATA 194,40,0,215,32,0
1050 DATA 205,30,6,205,183,5
1060 DATA 205,93,117,205,162,117
1070 DATA 195,193,5,33,122,117
1080 DATA 58,218,22,254,255,40
1090 DATA 3,33,142,117,6,5
1100 DATA 94,35,86,35,126,18
1110 DATA 35,126,19,18,35,16
1120 DATA 243,201,166,117,232,15
1130 DATA 178,117,247,23,164,118
1190 DATA 196,18,171,118,169,24
1200 DATA 32,118,169,18,166,117
1210 DATA 165,16,178,117,50,21
1220 DATA 164,118,169,19,171,118
1230 DATA 235,21,32,118,142,19
1240 DATA 205,225,118,205,232,15
1250 DATA 205,225,118,34,253,118
1260 DATA 221,126,25,205,247,23
1270 DATA 33,50,0,34,201,92
1280 DATA 205,163,118,245,221,126
1290 DATA 41,198,3,33,201,92
1300 DATA 190,56,1,119,241,40
1310 DATA 64,56,16,1,0,2
1320 DATA 221,229,225,17,82,0
1330 DATA 25,205,204,118,40,1
1340 DATA 55,8,42,253,118,221
1350 DATA 229,6,10,221,126,71
1360 DATA 119,35,221,35,16,247
1370 DATA 221,225,221,126,68,119
1380 DATA 35,221,126,41,119,35
1390 DATA 221,70,67,203,40,203
1400 DATA 184,8,48,2,203,248
1410 DATA 112,35,34,253,118,33
1420 DATA 202,92,126,60,119,43
1430 DATA 190,56,163,221,203,24
1440 DATA 134,42,75,92,229,205
1450 DATA 169,18,209,42,75,92
1460 DATA 167,237,82,62,4,211
1470 DATA 254,237,91,253,118,25
1480 DATA 34,253,118,175,8,42
1490 DATA 253,118,229,221,225,221
1500 DATA 54,11,255,17,230,255
1510 DATA 25,235,33,13,0,25
1520 DATA 235,229,213,205,225,118
1530 DATA 209,167,237,82,225,48
1540 DATA 2,32,15,8,167,32
1550 DATA 216,58,72,92,15,15
1560 DATA 15,230,7,211,254,201
1570 DATA 229,213,215,84,31,56
1580 DATA 5,253,54,0,20,239
1590 DATA 6,13,26,190,56,15
1600 DATA 32,4,19,35,16,246
1610 DATA 209,225,235,1,243,255
1620 DATA 9,24,194,209,225,1
1630 DATA 13,0,9,235,9,235
1640 DATA 6,13,43,27,26,78
1650 DATA 119,121,18,16,247,8
1660 DATA 62,1,8,24,223,205
1670 DATA 196,18,17,27,0,25
1680 DATA 205,169,24,1,14,0
1690 DATA 205,204,118,40,2,55
1700 DATA 201,221,203,67,70,32
1710 DATA 13,221,126,67,221,182
1720 DATA 70,230,2,200,62,255
1730 DATA 183,201,175,201,229,30
1740 DATA 0,123,134,35,206,1
1750 DATA 40,1,61,95,11,120
1760 DATA 177,32,242,123,190,225
1770 DATA 201,42,75,92,126,254
1780 DATA 128,32,5,253,54,0
1790 DATA 1,239,254,218,32,5
1800 DATA 1,8,0,9,201,215
1810 DATA 184,25,235,24,231,0
1820 DATA 0,0,208,1
This is the machine code installer for those of you who don't have the luxury of an assembler. If you are using this program then the assembler listing below is unnecessary. Note that line 130 [in the "repair" listing below] will only be accepted with the machine code entered and by RANDOMIZE USR 30000.


       ORG 30000
       LD   HL,NEWVEC
       LD   (VECTOR),HL ;alter vector
       LD   BC,0
       RET
NEWVEC ADD  A,206
       CP   "*"
       JP   NZ,#01F0
       RST  #10
       DEFW #20 ;next char
       OR   #20 ;make it l.c.
       CP   "l"
       JP   NZ,#0028 ;error if not L
       RST  #10
       DEFW #20 ;next char
       CALL #061E ;eval BC
       CALL #05B7 ;check end
       CALL WATROM ;redo to suit ROM
       CALL FIND
       JP   #05C1
;
;modify routine for different ROMs
WATROM LD   HL,OLDROM
       LD   A,(#16DA)
       CP   #FF
       JR   Z,YESOLD
       LD   HL,NEWROM ;to suit new ROM
YESOLD LD   B,5 ;number of CALLs to alter
REDOLP LD   E,(HL)
       INC  HL
       LD   D,(HL) ;DE=CALL+1
       INC  HL
       LD   A,(HL)
       LD   (DE),A
       INC  HL
       LD   A,(HL)
       INC  DE
       LD   (DE),A ;alter CALL
       INC  HL
       DJNZ REDOLP
       RET
;data table for old ROM
OLDROM DEFW L1+1,#0FEB ;CREATM
       DEFW L2+1,#17F7 ;MOTOR
       DEFW NXHDBF+1,#12C4 ;NEXTHD
       DEFW L4+1,#18A9 ;RDBYTS
       DEFW L5+1,#12A9 ;CLOSEM
;data table for new ROM
NEWROM DEFW L1+1,#10A5 ;CREATM
       DEFW L2+1,#1532 ;MOTOR
       DEFW NXHDBF+1,#13A9 ;NEXTHD
       DEFW L4+1,#15EB ;RDBYTS
       DEFW L5+1,#138E ;CLOSEM
;FIND
;finds all duff sectors
FIND   CALL FINDZ$ ;check Z$ is there
L1     CALL CREATM ;create M area
       CALL FINDZ$
       LD   (FMARK),HL ;zero pointer
       LD   A,(IX+25)
L2     CALL MOTOR ;switch on
       LD   HL,50
       LD   (SECTOR),HL ;minimum 50 sectors
FLOOP  CALL NXHDBF ;next header & buffer
       PUSH AF
       LD   A,(IX+41) ;SECNO
       ADD  A,3
       LD   HL,SECTOR
       CP   (HL)
       JR   C,LESS ;skip if less
       LD   (HL),A ;else store new sector length
LESS   POP  AF
       JR   Z,NEXT ;if not used
       JR   C,ISBAD ;if 1st checksum fails
       LD   BC,#0200
       PUSH IX
       POP  HL
       LD   DE,#0052
       ADD  HL,DE
       CALL CHKSUM
       JR   Z,ISBAD ;if 2nd checksum OK too
       SCF  ;if 2nd bad
ISBAD  EX   AF,AF' ;save good/bad flag
       LD   HL,(FMARK)
       PUSH IX
       LD   B,10
LPNAM  LD   A,(IX+71)
       LD   (HL),A ;copy name into free area
       INC  HL
       INC  IX
       DJNZ LPNAM
       POP  IX
       LD   A,(IX+68)
       LD   (HL),A ;store RECNUM
       INC  HL
       LD   A,(IX+41)
       LD   (HL),A ;store HDNUM
       INC  HL
       LD   B,(IX+67)
       SRA  B ;shift to lose bit 0
       RES  7,B
       EX   AF,AF'
       JR   NC,CHKOK2
       SET  7,B ;if checksum failed
CHKOK2 LD   (HL),B ;store flag
       INC  HL
       LD   (FMARK),HL
NEXT   LD   HL,SECTOR+1
       LD   A,(HL)
       INC  A
       LD   (HL),A ;inc count
       DEC  HL
       CP   (HL)
       JR   C,FLOOP ;if more to do
       RES  0,(IX+24) ;ensure read file
       LD   HL,(VARS)
       PUSH HL
L5     CALL CLOSEM ;close area & motor off
       POP  DE
       LD   HL,(VARS)
       AND  A
       SBC  HL,DE ;HL=change in VARS
       LD   A,4
       OUT  (#FE),A ;green border while sorting
       LD   DE,(FMARK)
       ADD  HL,DE ;decrease FMARK suitably
       LD   (FMARK),HL
;THE BUBBLE SORT
;(no prizes for speed)
LENGTH EQU  13 ;=items in each record
SORT   XOR  A
       EX   AF,AF' ;zero sort flag
       LD   HL,(FMARK)
       PUSH HL
       POP  IX
       LD   (IX+11),#FF ;insert end marker
       LD   DE,-2*LENGTH
       ADD  HL,DE
       EX   DE,HL
       LD   HL,LENGTH
       ADD  HL,DE
       EX   DE,HL ;DE=lowest element, HL=1 above
STLP   PUSH HL
       PUSH DE
       CALL FINDZ$
       POP  DE
       AND  A
       SBC  HL,DE
       POP  HL ;Z if at start
       JR   NC,ENDED
       JR   NZ,NENDS
ENDED  EX   AF,AF' ;end of pass
       AND  A
       JR   NZ,SORT ;if nor finished
       LD   A,(BORDCR)
       RRCA
       RRCA
       RRCA
       AND  #07
       OUT  (#FE),A ;restore border colour
       RET  ;then exit
NENDS  PUSH HL
       PUSH DE
       RST  #10
       DEFW #1F54 ;check break
       JR   C,NBRK
       LD   (IY+0),#14 ;force Break error
       RST  #28
NBRK   LD   B,LENGTH
ORDCHK LD   A,(DE)
       CP   (HL)
       JR   C,SWAP ;if not in order
       JR   NZ,NOTSAM ;if different
       INC  DE
       INC  HL
       DJNZ ORDCHK ;do all chars
NOTSAM POP  DE
       POP  HL
NXTBUB EX   DE,HL ;move DE up
       LD   BC,-LENGTH
       ADD  HL,BC ;move HL up
       JR   STLP
;
SWAP   POP  DE
       POP  HL
       LD   BC,LENGTH
       ADD  HL,BC
       EX   DE,HL
       ADD  HL,BC
       EX   DE,HL
       LD   B,LENGTH
SWLP   DEC  HL
       DEC  DE
       LD   A,(DE)
       LD   C,(HL)
       LD   (HL),A
       LD   A,C
       LD   (DE),A ;swap bytes
       DJNZ SWLP ;do all chars
       EX   AF,AF'
       LD   A,1 ;show 'sorted'
       EX   AF,AF'
       JR   NXTBUB
;
;read next sector
;Z if not used
;else C if check fails
;or NC if check OK
NXHDBF CALL NEXTHD ;next header
       LD   DE,#001B
       ADD  HL,DE
L4     CALL RDBYTS ;read the data
       LD   BC,#000E
       CALL CHKSUM
       JR   Z,CHKOK
       SCF  ;if check fails
       RET
CHKOK  BIT  0,(IX+67)
       JR   NZ,BAD
       LD   A,(IX+67)
       OR   (IX+70)
       AND  2
       RET  Z ;if not used
       LD   A,#FF
       OR   A
       RET  ;if sector OK
BAD    XOR  A
       RET
;
;CHECKSUM CALCULATOR
CHKSUM PUSH HL
       LD   E,0
L134C  LD   A,E
       ADD  A,(HL)
       INC  HL
       ADC  A,1
       JR   Z,L1354
       DEC  A
L1354  LD   E,A
       DEC  BC
       LD   A,B
       OR   C
       JR   NZ,L134C
       LD   A,E
       CP   (HL)
       POP  HL
       RET
;
;FIND Z$
;returns with HL=start of elements in Z$
FINDZ$ LD   HL,(VARS)
VARLP  LD   A,(HL)
       CP   128
       JR   NZ,MORVAR ;if more variables left
       LD   (IY+0),#01
       RST  #28 ;"Variable not found"
MORVAR CP   "Z"+128
       JR   NZ,NOTZ$
       LD   BC,8
       ADD  HL,BC ;skip over other bytes in array
       RET
NOTZ$  RST  #10
       DEFW #19B8 ;get next variable start
       EX   DE,HL
       JR   VARLP ;try again with next one
;CONSTANTS
CREATM EQU  #0FE8
MOTOR  EQU  #17F7
NEXTHD EQU  #12C4
RDBYTS EQU  #18A9
CLOSEM EQU  #12A9
BORDCR EQU  #5C48
VARS   EQU  #5C4B
VECTOR EQU  #5CB7
SECTOR EQU  #5CC9
PRTBC  EQU  #1A1B
FMARK  DEFW 0
TEMPA  DEFB 0
*D+
*L+
       DEFW $-30000
The assembly listing (above) and the Basic program (below) are both required to be in memory before you run the program. The code needs to be organised from 30000 if you are using an assembler and the code can be saved as SAVE "SL.CODE" CODE 30000,466.


100 INPUT "Drive number ";d
110 IF d<1 OR d>8 THEN GO TO 100
Lines 100-110 Get the drive number.
120 DIM z$(200,13): DIM n$(13)
Line 120 Initialises the arrays.
130 *L d
Line 130 Calls the machine code via the Shadow ROM.
135 PRINT "Wait a sec ..."
139 REM find last item
140 FOR i=1 TO 200
150 IF Z$(i,12)<>CHR$ 255 THEN NEXT i
160 LET n=i-1
Lines 135-160 The beginning of the initialisation loop.
169 REM ove repetitions
170 FOR i=2 TO n
180 IF z$(I)<>z$(i-1) THEN GO TO 190
182 IF CODE z$(i,13)>127 THEN LET z$(i)=n$: GO TO 190
184 LET z$(i-1)=n$
190 NEXT i
Lines 169-190 A complex nested loop to sort the file information.
200 LET c=2
1000 CLS: PRINT INVERSE 1;"      MICRODRIVE REPAIR KIT     "
1005 PRINT ''"0. Output to ";"printer" AND c=2;"screen" AND c=3
1010 PRINT '"1. Full catalogue"
1020 PRINT '"2. Sector list"
1030 PRINT '"3. Bad sector list"
1040 PRINT '"4. Check file"
1090 PRINT
Lines 200-1090 The menu for the main routines.
1095 INPUT ;: PRINT #0;"Choose an option";
1100 PAUSE 0: LET a$=INKEY$
1110 IF a$<"0" OR a$>"4" THEN GO TO 1100
1120 IF a$="0" THEN LET c=5-c: GO TO 1000
1130 GO SUB 1000+1000*VAL a$
1140 IF c=2 THEN PRINT "Press any key for menu": PAUSE 0
1150 GO TO 1000
Lines 1095-1150 Read the keyboard and perform the relevant operation.
1999 REM Full catalogue
2000 PRINT #c;"Full catalogue"
2010 DIM f$(10): LET f$=z$(1)
2020 FOR i=1 TO n
2030 IF z$(i)=n$ OR z$(i, TO 10)=f$ THEN GO TO 2060
2040 PRINT #c;f$( TO 10)
2050 LET f$=z$(i, TO 10)
2060 NEXT i
2065 PRINT #c;z$(n, TO 10)
2070 RETURN
Lines 1999-2070 The routine which prints out a full catalogue of the cartridge.
2999 REM Complete sector list
3000 DIM f$(10): INPUT "Filename (or ENTER for all):",f$
3010 FOR i=1 TO n
3015 IF z$(i)=n$ THEN GO TO 3030
3020 IF f$=n$( TO 10) THEN GO SUB 9500: GO TO 3030
3025 IF z$(i, TO 10)=f$ THEN GO SUB 9500
3030 NEXT i
3040 RETURN
Lines 2999-3040 This routine prints a full sector list of the cartridge.
3999 REM Bad sector list
4000 PRINT #c;"Bad sector list"
4010 FOR i=1 TO n
4020 IF z$(i)<>n$ AND z$(i,13)>CHR$ 127 THEN GO SUB 9500
4030 NEXT i
4040 RETURN
Lines 3999-4040 This routine prints a select list of the bad sectors on the cartridge.
4999 REM Check file
5000 DIM f$(10): INPUT "Filename? ";f$
5005 DIM x$(256): LET eof=-1: LET good=1
5010 FOR i=1 TO n
5020 IF z$(i, TO 10)<>f$ THEN GO TO 5100
5030 IF z$(i,13)>CHR$ 127 THEN GO TO 5060
5039 REM good sector
5040 LET x$(CODE z$(i,11)+1)="y"
5050 GO TO 5080
5059 REM bad sector
5060 PRINT #c;"Record ";CODE z$(i,11);" bad on sector ";CODE z$(i,12)
5070 LET good=0
5080 LET a=CODE z$(i,13)
5090 IF a/2<>INT (a/2) THEN LET eof=CODE z$(i,11)
5100 NEXT i
5110 IF eof>=0 THEN GO TO 5120
5111 REM missing EOF
5112 FOR i=200 TO 1 STEP -1
5114 IF x$(i)=" " THEN NEXT i
5116 LET eof=i-1
5118 LET good=0: PRINT #c;"No EOF record"
5120 FOR i=0 TO eof
5130 IF x$(i+1)=" " THEN PRINT "Record ";i;" missing": LET good=0
5140 NEXT i
5150 IF good=1 THEN PRINT "File ";f$;" intact"
5160 IF good=0 THEN PRINT "File ";f$;" corrupted"
5170 RETURN
Lines 4999-5170 Break down each individual file.
9000 CLEAR 29999: LOAD *"m";1;"SL.CODE" CODE: RANDOMIZE USR 30000: RUN
Line 9000 Loads and calls the machine code.
9500 PRINT #c;z$(i, TO 10);" r";CODE z$(i,11);TAB 16;"s";CODE z$(i,12);TAB 21;
9510 LET f=CODE z$(i,13)
9520 IF f/2<>INT (f/2) THEN PRINT #c;"EOF";
9530 IF CODE z$(i,13)>127 THEN PRINT #c;TAB 25;"BAD";
9540 PRINT #c
9550 RETURN
Lines 9500-9550 The subroutine used to display file status; ie. whether or not it's corrupted.
Home Contents KwikPik