.TITLE FIX_BOOK - Fix .DECW$BOOK or .BKB file after download .IDENT /X01.03/ ;+ ; FIX_BOOK.MAR - Try to fix mangled (by http: download) .DECW$BOOK or .BKB file ; ; Description: ; ; OpenVMS ODLs (Online Documentation Library CD files) are being presented ; at http://odl.sysworks.biz. When downloading .DECW$BOOK or .BKB files from ; this site, they are unsuable, because the download removes the required ; record format (sequential file with variable length records) through the ; download as octet-stream and/or the copy operation with FTP or sftp. ; ; This program tries to reconstruct a valid .DECW$BOOK or .BKB bookreader file ; ; Initiated by note EISNER::VMS 3726 ; ; ; Build with: ; ; $ MACRO fix_book ! for OpenVMS VAX ; $ MACRO/MIGRATE fix_book ! for OpenVMS Alpha, I64 or x86_64 ; $ LINK fix_book ; ; Run with: ; ; $ DEFINE INFILE filename.DECW$BOOK (or filename.BKB) ; $ DEFINE OUTFILE new_filename.DECW$BOOK ; $ RUN fix_book ; ; Parameters: ; ; Input: INFILE - logical pointing to mangled .DECW$BOOK/.BKB ; Output: OUTFILE - logical pointing to reconstructed .DECW$BOOK/.BKB ; ; 25-MAR-2021 Volker Halle [ halle (at) encompasserve.org ] ; ; Modification history: ; ; X01.00 VH Initial test release ; ; X01.01 VH Do not specify RAT in OUTRAB (force Record Attributes = none) ; 26-MAR-2021 Check for RFM=VAR and output appropriate SET FILE/ATTR message ; Refer to 'bytes on disk' in good_book message ; ; X01.02 VH Fix 'end of file byte' discrepancy in LAST record ; 27-MAR-2021 Output no. of parts in book together with title ; ; X01.03 VH Fix %MACRO-E-BRDESTRANG, Branch destination out of range errors ; 29-MAR-2021 to make FIX_BOOK also work with OpenVMS VAX macro assembler ;- .PSECT DATA,WRT,NOEXE INFAB: $FAB FNM = ,- ; Input file name FAC = ; Block I/O read operations INRAB: $RAB FAB = INFAB,- ; Pointer to FAB BKT = 0,- ; Start with current block UBF = REC_BUFF,- ; Record buffer USZ = REC_SIZE ; and size OUTFAB: $FAB FNM = ,- ; Output file name FOP = CBT,- ; Try for contiguous file MRS = 0,- ; Maximum record size FAC = ; Record I/O write operations OUTRAB: $RAB FAB = OUTFAB,- ; Pointer to FAB BKT = 0,- ; Start with current block RBF = REC_BUFF ; Output uses same buffer ; as input REC_SIZE = 62*1024 ; Maximum record size REC_BUFF: .BLKB REC_SIZE ; Record buffer end_of_rec_buff: .ADDRESS . ; pointer to end of REC_BUFF vbn: .LONG 1 ; starting VBN for $READ good_str: .ASCID /%FIX_BOOK-I-GOOD, looks like a good .DECW$BOOK (bytes on disk)/ var_str: .ASCID \%FIX_BOOK-W-NOT_VAR, use SET FILE/ATTRIB=(RFM=VAR,RAT=NONE) infile\ bad_str: .ASCID /%FIX_BOOK-E-BAD, does not look like a mangled .DECW$BOOK/ title_str: .ASCID /%FIX_BOOK-I-TITLE, book title: !AC (!UL parts)/ rec_str: .ASCID /%FIX_BOOK-I-RECORD, record: !UL starts with !XL/ bad_rec: .ASCID /%FIX_BOOK-E-BAD_REC, unexpected start of record/ read_str: .ASCID /%FIX_BOOK-I-READ, reading !UL bytes starting at VBN !UL/ FAOOUT: .WORD 256 .BYTE DSC$K_DTYPE_T .BYTE DSC$K_CLASS_S .ADDRESS .+4 .BLKB 256 record: .LONG 1 ; Record no. (starting at 1) rec_ptr: .BLKL 1 ; pointer to next record in REC_BUFF n_parts: .BLKL 1 ; no. of parts in book .PSECT CODE,NOWRT,EXE .ENTRY FIX_BOOK,^M<> ; No registers to save $OPEN FAB=INFAB BLBC R0,EXIT1 $CONNECT RAB=INRAB BLBC R0,EXIT2 MOVL INFAB+FAB$L_ALQ,- OUTFAB+FAB$L_ALQ $CREATE FAB=OUTFAB BLBC R0,EXIT3 $CONNECT RAB=OUTRAB BLBC R0,EXIT4 BRB READ EXIT1: MOVL INFAB+FAB$L_STS,R2 MOVL INFAB+FAB$L_STV,R3 BRW EXIT EXIT2: MOVL INRAB+RAB$L_STS,R2 MOVL INFAB+RAB$L_STV,R3 BRW EXIT EXIT3: MOVL OUTFAB+FAB$L_STS,R2 MOVL OUTFAB+FAB$L_STV,R3 BRW EXIT EXIT4: MOVL OUTFAB+RAB$L_STS,R2 MOVL OUTFAB+RAB$L_STV,R3 BRW EXIT READ: MOVW #256, faoout ; reset buffer length $FAO_S CTRSTR=read_str,- OUTBUF=faoout,- OUTLEN=faoout,- P1=INRAB+RAB$W_USZ,- ; no. of bytes to read P2=vbn ; starting VBN PUSHAQ faoout CALLS #1, G^LIB$PUT_OUTPUT $READ RAB=INRAB BLBS R0,check_book CMPL R0,#RMS$_EOF BEQL 80$ BRW EXIT2 80$: BRW done ; branch aid for VAX good_book: ; looks like a valid .DECW$BOOK file PUSHAQ good_str CALLS #1,G^LIB$PUT_OUTPUT CMPB INFAB+FAB$B_RFM, #FAB$C_VAR ; RFM = variable length records ? BEQL 90$ PUSHAQ var_str CALLS #1,G^LIB$PUT_OUTPUT 90$: BISL #FAB$M_DLT,OUTFAB+FAB$L_FOP ; delete OUTFILE on close BRW done bad_book: ; not an octet-stream mangled .DECW$BOOK PUSHAQ bad_str CALLS #1,G^LIB$PUT_OUTPUT BRW done check_book: CMPW #^x03FE,rec_buff ; correct 1st hdr record length ? BEQL good_book ; EQL -> yes -> exit CMPW #1,rec_buff ; octet-stream .DECW$BOOK ? BNEQ bad_book ; NEQ -> no ; ; Special handling of first and possibly second record in mangled .DECW$BOOK ; MOVL rec_buff+^x46, n_parts ; no. of parts in book MOVW #256, faoout ; reset buffer length $FAO_S CTRSTR=title_str,- OUTBUF=faoout,- OUTLEN=faoout,- P1=#rec_buff+^x7E,- ; ptr to title P2=n_parts ; no. of parts in book PUSHAQ faoout CALLS #1, G^LIB$PUT_OUTPUT MOVW #256, faoout ; reset buffer length $FAO_S CTRSTR=rec_str,- OUTBUF=faoout,- OUTLEN=faoout,- P1=record,- ; record number P2=rec_buff ; 1st longword of record PUSHAQ faoout CALLS #1, G^LIB$PUT_OUTPUT CMPW rec_buff,#1 ; check 1st record type field BEQL 1$ PUSHAQ bad_rec CALLS #1, G^LIB$PUT_OUTPUT BISL #FAB$M_DLT,OUTFAB+FAB$L_FOP ; delete OUTFILE on close BRW done 1$: MOVW #^x03FE,OUTRAB+RAB$W_RSZ ; 1st record is 0x3FE long MOVAB rec_buff,OUTRAB+RAB$L_RBF ; start of offset 0 $PUT RAB=OUTRAB ; output 1st HDR record BLBS R0, 2$ BRW EXIT4 2$: MOVAL rec_buff+^x03FE,rec_ptr ; point to next record CMPW #2,rec_buff+^x03FE ; 2nd header type field = 2 ? BNEQ copy_records INCL record ; increment record number MOVW #256, faoout ; reset buffer length $FAO_S CTRSTR=rec_str,- OUTBUF=faoout,- OUTLEN=faoout,- P1=record,- ; record number P2=rec_buff+^x03FE ; 1st longword of record 2 PUSHAQ faoout CALLS #1, G^LIB$PUT_OUTPUT ADDL2 #^x03FE,OUTRAB+RAB$L_RBF ; point to hdr 2 $PUT RAB=OUTRAB ; output 2nd HDR record BLBS R0, 5$ BRW EXIT4 5$: ADDL2 #^x03FE, rec_ptr ; point to next record ; ; Copy remaining records of a mangled .DECW$BOOK file ; ; rec_ptr - point to start of next record in REC_BUFF ; contents of @rec_ptr should be xxxxtttt ; with xxxx = length of next record in bytes ; tttt = type of record copy_records: MOVL rec_ptr, R2 ; address of next record ADDL2 #2, R2 ; point to size word MOVZWL (R2), OUTRAB+RAB$W_RSZ ; size of next record MOVZWL (R2), R3 ; record size ADDL2 rec_ptr, R3 ; point past next record CMPL R3, end_of_rec_buff ; outside REC_BUFF ? BGTRU 11$ ; next record inside REC_BUFF MOVL rec_ptr,OUTRAB+RAB$L_RBF ; start of record INCL record ; increment record number MOVW #256, faoout ; reset buffer length $FAO_S CTRSTR=rec_str,- OUTBUF=faoout,- OUTLEN=faoout,- P1=record,- ; record number P2=@rec_ptr ; 1st longword of record PUSHAQ faoout CALLS #1, G^LIB$PUT_OUTPUT TSTL @rec_ptr ; NULL = end-of-file BEQL 19$ ; if EQL -> done CMPW @rec_ptr, #0 ; must be > 0 BLEQ 18$ CMPW @rec_ptr, #32 ; check record type field < 33 ? BLEQ 20$ BRB 18$ 11$: BRW 10$ ; branch aid for VAX 18$: PUSHAQ bad_rec CALLS #1, G^LIB$PUT_OUTPUT BISL #FAB$M_DLT,OUTFAB+FAB$L_FOP ; delete OUTFILE on close 19$: BRW done ; branch aid for VAX 20$: CMPW @rec_ptr, #5 ; 5 = LAST record ? BNEQ 25$ ; ; for the LAST record to be written, correctly determine record length ; ; The last record looks like this (from hyperreader.c): ; ; "Last" Record ; ------------- ; variable size ; part type: short 0x00 (type=0x0005) ; part length: long 0x02 ; then starting at offset 0x06: ; (1 .. number of parts in the book) series of 10 byte units, each: ; VBN (512) of part long 0x00 ; position in VBN of part short 0x04 ; length of this part long 0x06 ; MULL3 n_parts, #10, R2 ; no. of parts times 10 bytes ADDL2 #6, R2 ; add part type + length MOVL R2, OUTRAB+RAB$W_RSZ ; actual size of LAST record 25$: $PUT RAB=OUTRAB ; output record BLBS R0, 26$ BRW EXIT4 26$: CMPW @rec_ptr, #5 ; 5 = LAST record written ? BEQL 27$ ; EQL: yes -> done MOVL R3, rec_ptr ; point to next record BRW copy_records ; copy next record 27$: BRW done ; branch aid for VAX ; ; current record exceeds end of data in REC_BUFF ; copy record data from @rec_ptr until @end_of_rec_buff to start of REC_BUFF, ; then read next blocks from INFILE, then output current record ; 10$: MOVL end_of_rec_buff, R2 SUBL2 rec_ptr, R2 ; no of bytes to copy PUSHL R2 MOVC3 R2, @rec_ptr, rec_buff POPL R2 MOVAL rec_buff, rec_ptr ; point to beginning of REC_BUFF ADDL2 #rec_buff, R2 ; point past copied part of record MOVL R2, INRAB+RAB$L_UBF ; set input read buffer address SUBL3 R2, #end_of_rec_buff, R2 ; get no. of bytes to read DIVL2 #512, R2 ; get rounded no. of blocks ADDL2 R2, vbn ; starting VBN for next $READ MULL2 #512, R2 ; set no. of bytes to read MOVW R2, INRAB+RAB$W_USZ ; set input buffer size MOVL INRAB+RAB$L_UBF,end_of_rec_buff ADDL2 R2, end_of_rec_buff ; new end of rec_buff MOVZWL INRAB+RAB$W_USZ, R2 MOVW #256, faoout ; reset buffer length $FAO_S CTRSTR=read_str,- OUTBUF=faoout,- OUTLEN=faoout,- P1=R2,- ; no. of bytes to read P2=vbn ; starting VBN PUSHAQ faoout CALLS #1, G^LIB$PUT_OUTPUT $READ RAB=INRAB BLBC R0, 12$ BRW copy_records ; process next record 12$: CMPL R0,#RMS$_EOF BEQL DONE BRW EXIT2 DONE: $CLOSE FAB=INFAB $CLOSE FAB=OUTFAB RET EXIT: PUSHL R3 PUSHL R2 CALLS #2, G^LIB$SIGNAL RET .END FIX_BOOK