| Home | Contents | KwikPik |
| R U N N I N G R E P A I R S | |||||
|---|---|---|---|---|---|
| |||||
|
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 SECTORSBefore 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 ANALYSISThe 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 ACTIONThe 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. | |||
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. | |
| Home | Contents | KwikPik |