--- title: "VU-File Patch" id: 57419 type: "computer_media" slug: "vu-file-patch" url: "http://localhost/computer_media/vu-file-patch/" markdown_url: "http://localhost/computer_media/vu-file-patch.md" published_at: "2024-10-05T12:59:51+00:00" modified_at: "2026-04-03T07:58:30+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/10/220_VUFP.png" excerpt: "A drop-in extension for the VU-FILE database adds printer formatting, page headers, margins, and field layouts through direct memory patching and machine code hooks." category: - name: "Archived Media" slug: "archived-media" taxonomy: "category" url: "http://localhost/category/archived-media/" post_tag: - name: "Downloadable" slug: "downloadable" taxonomy: "post_tag" url: "http://localhost/tag/downloadable/" - name: "TS 1000" slug: "ts1000" taxonomy: "post_tag" url: "http://localhost/tag/ts1000/" model: - name: "Timex/Sinclair 1000" slug: "ts-1000" taxonomy: "model" url: "http://localhost/model/ts-1000/" indiv: - name: "Tim Ward" slug: "tim-ward" taxonomy: "indiv" url: "http://localhost/indiv/tim-ward/" genre: - name: "Database" slug: "database" taxonomy: "genre" url: "http://localhost/type/database/" media_contents: - id: 56736 title: "Timex Sinclair Public Domain Library Tape 1005" type: "computer_media" url: "http://localhost/computer_media/timex-sinclair-public-domain-library-tape-1005/" media_type: "Program" programmers: - name: "Tim Ward" slug: "tim-ward" taxonomy: "indiv" url: "http://localhost/indiv/tim-ward/" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2024/10/220_VUFP.png" related_products: - id: 14547 title: "VU-FILE (TS 1000)" type: "product" url: "http://localhost/product/vu-file/" - id: 14351 title: "The Organizer" type: "product" url: "http://localhost/product/the-organizer/" media_type_tags: "Database" --- # VU-File Patch This program is an extension module for the VU-FILE database system, designed to be merged into an existing VU-FILE program by deleting lines 52 and above and then merging. It provides a five-option menu covering record format setup, data entry, cassette save, printer format configuration, and data clearing. The printer formatting routines handle page headers, top/bottom margins, page numbering, and field layout across multiple print lines per record, with expanded (double-width via CHR$ 14/15 control codes) header line support. Several USR calls delegate core database logic to machine code routines at addresses such as 17986, 19157, 19154, and 10655, while PEEK of system variables at addresses like 16400/16401 is used to locate the start of the BASIC program area for direct memory patching of the DIM F$ array descriptor. *** ## Program Analysis ### Program Structure The program is structured as a merge module for an existing VU-FILE database application. The instructions in lines 1000–1010 explicitly state that lines 52 and above of VU-FILE should be deleted before merging. The module then provides a complete replacement for the upper half of the combined program. 1. **Lines 1020–1260:** Initialisation — dimensions the main data array `F$`, patches the array descriptor in memory, sets global variables, and seeds the first 20 characters of `F$` with “VU-FILE+”. 2. **Lines 1270–1290:** Calls machine code to initialise hardware, then jumps to the main menu. 3. **Lines 1300–1510:** Main menu — presents five options and dispatches via a series of `IF`/`GOTO` chains. 4. **Lines 1520–1660:** Option handlers for format setup (USR 17989), data entry loop (USR 19157/19160), and print-to-printer (USR 17983). 5. **Lines 1670–1800:** Save routine — accepts a filename, sets tape player, calls USR 19154 to prepare, then uses `SAVE`. 6. **Lines 1810–2500:** Printer format configuration — collects page dimensions, margins, header lines, page numbering preference, and per-field layout data into arrays `H`, `H$`, and `E`. 7. **Lines 2510–3040:** Print subroutines — `GOSUB 2510` is the main record-printing engine; subordinate routines handle header printing, page-break and margin logic, and individual line output via USR 10655. 8. **Lines 3050–3080:**`BL` (blank) subroutine — clears three screen lines at rows 11–13 used for prompting. 9. **Lines 3090–3120:** Tape save and `LIST` — saves the program under two names and lists it. ### Memory Patching of the DIM Descriptor Lines 1080–1160 perform direct surgery on the `F$` array descriptor in memory. The program calculates the address of the array header using `PEEK 16400 + 256 * PEEK 16401` (the ZX81 VARS system variable), then writes the correct byte-length fields into the descriptor at offsets `L+1` through `L+6`. This is necessary because the `DIM F$(9500,4)` at line 1030 allocates 9500 rows of 4 characters each, but the intent is to use it as 9500 rows of 4 bytes — the patches rewrite the dimension descriptors to match the intended record layout expected by the machine code routines. The arithmetic uses `B=256` and the standard low-byte/high-byte decomposition pattern: `A - B*INT(A/B)` for the low byte and `INT(A/B)` for the high byte, avoiding the `MOD` operator (not available in ZX81 BASIC). ### Machine Code Interface All heavy lifting is delegated to machine code routines. The BASIC acts as a configuration and control shell. | Address | Called from | Purpose (inferred) | | --- | --- | --- | | `17986` | 1270 | Hardware/display initialisation | | `17989` | 1520 | Set record format (Option 1) | | `19157` | 1540 | VU-FILE data entry; returns status in `A` | | `19160` | 1610 | Advance to next record in data entry loop | | `17983` | 1650 | Print/output records | | `19154` | 1770, 2470 | Prepare for tape save | | `19151` | 1790, 3100 | Post-save status check | | `17992` | 2260 | Data format setup | | `10655` | 2830, 2920, 3000 | Send line `P$` to printer | | `10660` | 2700, 2740 | Send expanded header line to printer | The `RAND USR` idiom (rather than `LET x = USR`) is used when the return value is not needed, discarding the result cleanly without a variable assignment. An unusual pattern appears at lines 2690, 2700, 2820, and 2830: `RAND CODE "P"` immediately before `RAND USR`. `CODE "P"` evaluates to 80 (ASCII for ‘P’), so `RAND 80` seeds the random number generator as a side effect; the real purpose appears to be passing the ASCII value of “P” (the printer stream number or a parameter) to the machine code routine that follows — a creative way to communicate a constant to an MC routine without dedicated variable overhead. ### Print Formatting Engine The subroutine starting at line 2510 is the record printer. It is called once per field (driven by the MC data-entry loop at 1650) and accumulates field data from `F$(21 TO ...)` into a line buffer array `R$(RPL, LL)`. When all `NF` fields have been processed (`CF=NF`), it outputs `RPL` lines to the printer and resets. Page break detection at line 2540 checks whether the remaining lines on the page are fewer than `RPL`, triggering the bottom-margin and new-page routine at line 2860. Expanded (double-width) header lines are bracketed by `CHR$ 14` and `CHR$ 15` (the ZX81 expand/normal control codes) written into `P$` before calling the printer MC routine. ### Key BASIC Idioms - **`VAL "number"` in `GOTO`/`GOSUB`:** Used throughout (e.g. `GOTO VAL "1540"`) as a memory optimisation; storing the target as a string literal saves bytes compared to a numeric constant in the tokenised line. - **Named constants via variables:**`B=256`, `C=0`, `D=1` are set once and reused everywhere, saving tokenised bytes on repeated literals. - **`BL` variable for subroutine address:**`LET BL=3050` at line 1240 stores the line number of the blank-screen subroutine; `GOSUB BL` then uses a variable, which is shorter than `GOSUB VAL "3050"` when called many times. - **`M` variable for main menu return:**`LET M=1300` acts as a named entry point for returning to the menu, used with `GOTO M`. - **Inverse video title:** Line 1320 uses zmakebas `%X` escapes to display “PSION COMPUTERS” in inverse video, identifying the original software vendor. ### Anomalies and Points of Interest - Line 2850 has condition `IF PL-D <> FL-(MARGIN/2) THEN RETURN` inside what is also the entry point of the page-break subroutine (line 2860). The `GOSUB 2820`/`GOSUB 2830` path falls through to this check; the unconditional fall-through from line 2850 into 2860 when the condition is false means the page-break logic immediately executes — this appears intentional but makes the control flow non-obvious. - Line 2900 uses `W$(D, LL/2 TO )` — an open-ended slice to the end of the string — for placing the page number; this is valid ZX81 BASIC slice syntax. - The `FORMAT` flag (lines 1220, 2250, 2480–2490) distinguishes first-time format entry from subsequent editing, routing back to the format change menu (1810) after data-format setup if a format already existed. - Lines 3090–3120 save the program under two filenames — `"VU-FILE+"` (with inverse `+` for auto-run) and `"10220"` (also with inverse final character) — before issuing `LIST`, suggesting a development/distribution workflow where the author distributed both a named and a line-numbered copy. ## Source Code ``` 1000 REM DELETE LINES 52+ FROM VU-FILE AND MERGE THESE LINES OF BASIC IN WITH LINES 50 AND 51 OFVU-FILE. CONTACT TIM WARD IF YOU HAVE ANY QUESTIONS ABOUT THIS PGM. 1010 REM 1020 FAST 1030 DIM F$(9500,4) 1040 LET A=VAL "9500" 1050 LET B=VAL "256" 1060 LET C=VAL "0" 1070 LET D=VAL "1" 1080 LET L=PEEK 16400+B*PEEK 16401 1090 LET A=A*4+5 1100 POKE L+D,A-B*INT (A/B) 1110 POKE L+2,INT (A/B) 1120 POKE L+3,D 1130 LET A=A-3 1140 POKE L+4,A-B*INT (A/B) 1150 POKE L+5,INT (A/B) 1160 POKE L+6,C 1170 LET Z$=" " 1180 LET PL=C 1190 LET CF=C 1200 LET X$="N" 1210 LET HEAD=C 1220 LET FORMAT=C 1230 LET M=VAL "1300" 1240 LET BL=VAL "3050" 1250 LET F$( TO 20)="VU-FILE+" 1260 SLOW 1270 RAND USR VAL "17986" 1280 CLS 1290 GOTO VAL "1540" 1300 CLS 1310 SLOW 1320 PRINT AT 2,8;"%P%S%I%O%N% %C%O%M%P%U%T%E%R%S" 1330 PRINT " MODIFICATIONS BY TIM WARD" 1340 PRINT AT 5,12;"VU-FILE+",,,,, 1350 PRINT " 1)...SET RECORD FORMAT",,, 1360 PRINT " 2)...ENTER VU-FILE",,, 1370 PRINT " 3)...SAVE VU-FILE AND/OR DATA",,, 1380 PRINT " 4)...SET PRINTER FORMATS",,, 1390 PRINT " 5)...CLEAR ALL DATA/FORMATS",,,,, 1400 PRINT "PRESS 1 TO 5 FOR DESIRED OPTION" 1410 LET I$=INKEY$ 1420 IF I$<"1" OR I$>"5" THEN GOTO VAL "1410" 1430 LET PH=HEAD 1440 LET PAGE=C 1450 LET PL=C 1460 CLS 1470 IF I$="1" THEN GOTO VAL "1520" 1480 IF I$="2" THEN GOTO VAL "1540" 1490 IF I$="3" THEN GOTO VAL "1670" 1500 IF I$="4" THEN GOTO VAL "1810" 1510 IF I$="5" THEN RUN 1520 RAND USR VAL "17989" 1530 GOTO M 1540 LET A=USR VAL "19157" 1550 IF A=D THEN GOTO VAL "1580" 1560 IF A=VAL "2" THEN GOTO VAL "1630" 1570 GOTO M 1580 PRINT AT C,C;F$( TO 20);" ";PEEK VAL "16603" 1590 PRINT PEEK VAL "16565"+B*PEEK VAL "16566";TAB VAL "17";INT (VAL "100"*(PEEK VAL "18585"+B*PEEK VAL "16586")/(PEEK VAL "16583"+B*PEEK VAL "16584")) 1600 PRINT TAB VAL "20";PEEK VAL "16567" 1610 LET A=USR VAL "19160" 1620 GOTO VAL "1550" 1630 GOSUB VAL "2510" 1640 SLOW 1650 LET A=USR VAL "17983" 1660 GOTO VAL "1550" 1670 CLS 1680 FAST 1690 GOSUB BL 1700 PRINT "ENTER FILE NAME" 1710 INPUT N$ 1720 GOSUB BL 1730 PRINT "SET PLAYER TO RECORD" 1740 PRINT "BEFORE PRESSING NEWLINE" 1750 LET F$( TO 20)=N$ 1760 INPUT I$ 1770 RAND USR VAL "19154" 1780 SAVE N$ 1790 IF USR VAL "19151"<>C THEN STOP 1800 GOTO M 1810 IF FORMAT=C THEN GOTO VAL "1880" 1820 GOSUB BL 1830 PRINT "CHANGE P)AGE FORMAT D)ATA FORMATOR R)ETURN TO MAIN MENU" 1840 INPUT I$ 1850 IF I$="P" THEN GOTO VAL "1880" 1860 IF I$="D" THEN GOTO VAL "2260" 1870 IF I$="R" THEN GOTO M 1880 GOSUB BL 1890 PRINT "ENTER LINE LENGTH (1-132)" 1900 INPUT LL 1910 GOSUB BL 1920 PRINT "ENTER PAGE LENGTH (1-112)" 1930 INPUT FL 1940 GOSUB BL 1950 PRINT "ENTER NBR OF LINES FOR TOP AND BOTTOM MARGINS (0-111)" 1960 INPUT MARGIN 1970 LET LTP=FL-MARGIN 1980 GOSUB BL 1990 PRINT "HOW MANY HEADER LINES DO YOU WANT PRINTED? (0-10)" 2000 INPUT HL 2010 IF HL<>C THEN LET HEAD=D 2020 IF HL=C THEN GOTO VAL "2280" 2030 DIM H(HL) 2040 DIM H$(HL,LL) 2050 FOR Z=D TO HL 2060 GOSUB BL 2070 PRINT "EXPAND HEADER LINE ";Z;" (Y-N)" 2080 INPUT I$ 2090 LET H(Z)=C 2100 IF I$="Y" THEN LET H(Z)=D 2110 LET CM=LL 2120 IF I$="Y" THEN LET CM=LL/2 2130 GOSUB BL 2140 PRINT "CENTER HEADER LINE ";Z;" (Y-N)" 2150 INPUT J$ 2160 GOSUB BL 2170 PRINT "ENTER HEADER LINE ";Z,"(";CM;" CHARS MAX)" 2180 INPUT I$ 2190 IF J$="N" THEN LET H$(Z, TO CM)=I$ 2200 IF J$="Y" THEN LET H$(Z,INT (CM-LEN I$)/2 TO CM)=I$ 2210 NEXT Z 2220 GOSUB BL 2230 PRINT "PRINT PAGE NUMBERS (Y-N)" 2240 INPUT X$ 2250 IF FORMAT=D THEN GOTO VAL "1810" 2255 CLS 2260 RAND USR VAL "17992" 2270 CLS 2280 GOSUB BL 2290 PRINT "ENTER NBR OF PRINT LINES PER RECORD (1-10)" 2300 INPUT RPL 2310 GOSUB BL 2320 PRINT "ENTER NBR OF FIELDS IN RECORD (1-19)" 2330 INPUT NF 2340 DIM E(NF,3) 2350 FOR Z=D TO NF 2360 GOSUB BL 2370 PRINT "ENTER LENGTH OF FIELD ";Z 2380 INPUT E(Z,D) 2390 GOSUB BL 2400 PRINT "ENTER LINE TO PRINT FIELD ";Z,"ON (0-"+STR$ RPL+")" 2410 INPUT E(Z,2) 2420 GOSUB BL 2430 PRINT "ENTER STARTING COLUMN FOR FIELD NBR ";Z;" (1-"+STR$ (LL-E(Z,D)+D)+")" 2440 INPUT E(Z,3) 2450 NEXT Z 2460 FAST 2470 RAND USR VAL "19154" 2480 IF FORMAT=D THEN GOTO VAL "1810" 2490 LET FORMAT=D 2500 GOTO M 2510 FAST 2520 IF PL=C THEN GOSUB VAL "2950" 2530 LET CF=CF+D 2540 IF CF=D AND (FL-(MARGIN/2))-PLFL-(MARGIN/2) THEN RETURN 2860 LET CTR=PL+D 2870 LET PAGE=PAGE+D 2880 FOR Z=CTR TO FL 2890 DIM W$(D,LL) 2900 IF Z=FL-D AND X$="Y" THEN LET W$(D,LL/2 TO )=STR$ PAGE 2910 LET P$=W$(D) 2920 RAND USR VAL "10655" 2930 LET PL=PL+D 2940 NEXT Z 2950 LET PL=C 2960 LET PH=HEAD 2970 LET P$="" 2980 FOR Z=D TO MARGIN/2 2990 RAND CODE "P" 3000 RAND USR VAL "10655" 3010 LET PL=PL+D 3020 NEXT Z 3030 LET PH=HEAD 3040 RETURN 3050 PRINT AT 13,C;Z$ 3060 PRINT AT 12,C;Z$ 3070 PRINT AT 11,31;" " 3080 RETURN 3090 SAVE "VU-FILE%+" 3100 IF USR 19151=0 THEN RUN 3110 SAVE "1022%0" 3120 LIST ```