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 |