5200H - Installer Entry, Sign-On and Device Validation
The transient installer is entered here by the FILTER library command. Register Pair DE holds the address of the Device Control Block of the device being filtered, Register Pair HL holds the address of the parameter text that followed the filespec, and nothing else is defined. The block saves both pointers, displays the banner, and rejects the four conditions under which the filter cannot be installed.
5200
PUSH DE D5
Save Register Pair DE, which the FILTER command loaded with the address of the Device Control Block of the device being filtered (4025H, the *PR printer DCB, in normal use), onto the stack. It is recovered into Register Pair IX at 52E0H once the resident module has been copied, and is then used to hook the DCB.
5201
PUSH HL E5
Save Register Pair HL, which the FILTER command loaded with the address of the parameter text following the filter's filespec, onto the stack. It is brought back into Register Pair HL by the EX (SP),HL at 5222H, immediately before @PARAM is called.
5202
LD A,(DE) 1A
Fetch the DCB type byte, the first byte of the Device Control Block addressed by Register Pair DE, into Register A. In an LDOS Device Control Block this byte records both what the device can do and what has been done to it: bit 1 set means the device accepts output, bit 3 set means the device has been set to NIL and is not active, bit 4 set means the device has been routed to a disk file.
5203
PUSH AF F5
Save Register A, holding the DCB type byte just fetched, on the stack across the banner display, because the @DSPLY call at 5207H destroys it.
5204
Point Register Pair HL at the sign-on banner at 5375H, the text PR/FLT - Generalized Printer Filter - Version 5.3.1 followed by the MISOSYS copyright line and a 0DH terminator.
5207
GOSUB to the resident @DSPLY vector at 4467H in SYS0/SYS to display the 0DH-terminated string addressed by Register Pair HL (the banner at 5375H) on the video screen.
520A
POP AF F1
Restore Register A from the stack with the DCB type byte of the device being filtered, saved at 5203H.
520B
BIT 3,A CB 5F
Test bit 3 of Register A, the DCB type byte of the device being filtered. Bit 3 set means the device has been set to NIL, so it is not active and there is nothing to filter. The Z FLAG is set when the bit is clear, the NZ FLAG when it is set.
520D
Error Path
If the NZ FLAG has been set (bit 3 of the DCB type byte is set, so the device is not active), JUMP to 5360H to select the message Device not active! and abort.
5210
BIT 4,A CB 67
Test bit 4 of Register A, the DCB type byte of the device being filtered. Bit 4 set means the device has been routed to a disk file, and a routed device cannot also carry a filter.
5212
Error Path
If the NZ FLAG has been set (bit 4 of the DCB type byte is set, so the device is routed), JUMP to 536CH to select the message Device is routed! and abort.
5215
BIT 1,A CB 4F
Test bit 1 of Register A, the DCB type byte of the device being filtered. Bit 1 is the output capability bit, the one that corresponds to the device-type code 02H the ROM $PUT routine at 001BH hands to the central I/O dispatcher. A printer filter can only be applied to a device that accepts output.
5217
Error Path
If the Z FLAG has been set (bit 1 of the DCB type byte is clear, so the device does not accept output), JUMP to 5364H to select the message Not an output device! and abort.
521A
Point Register Pair HL at DFLAG$, the resident device-flag byte at 441FH in SYS0/SYS. Bit 3 of DFLAG$ is the flag that records whether the printer forms filter is installed; bit 0 records the spooler and bit 4 the KI/DVR keyboard driver.
521D
BIT 3,(HL) CB 5E
Test bit 3 of the byte addressed by Register Pair HL, which is DFLAG$ at 441FH. If it is already set the filter is resident and hooked, and installing a second copy would chain the filter to itself.
521F
Error Path
If the NZ FLAG has been set (DFLAG$ bit 3 is already set), JUMP to 5368H to select the message PR/FLT already active! and abort. The flag is cleared by RESET *PR, which is what makes a re-installation possible.
5222
EX (SP),HL E3
Exchange Register Pair HL, which holds 441FH (the address of DFLAG$), with the top word of the stack, which is the parameter-text pointer pushed at 5201H. Register Pair HL now addresses the parameter text again, and the stack slot holds 441FH ready for the POP HL at 524AH that sets the flag.
5223
Point Register Pair DE at the @PARAM keyword table at 5430H: 21 entries, each a 6-character blank-padded keyword followed by the 2-byte address of the cell that is to receive the value, ended by a 00H byte at 54D8H.
5226
GOSUB to the resident @PARAM vector at 4476H in SYS0/SYS. It parses the parameter text addressed by Register Pair HL against the keyword table addressed by Register Pair DE and deposits each value into the cell the table names, writing 0FFFFH for a switch that is present and 0000H for one that is absent or turned off. It returns with the Z FLAG set when everything in the text was recognised.
5229
Error Path
If the NZ FLAG has been set (@PARAM found something it could not match against the table at 5430H), JUMP to 5357H to raise parameter error 2CH and abort.
522CH - The PORT Parameter and the DFLAG$ Marker
PORT= is a Model I only parameter that moves printer output from the memory-mapped latch at 37E8H to a genuine Z80 I/O port. It is implemented by rewriting the two instructions in the built-in printer driver that touch 37E8H. Because the driver is three bytes wide at each point and a port instruction is two, the third byte is turned into a NOP.
522C
LD HL,0000H 21 00 00
Self-Modifying Code
Load Register Pair HL with the PORT= value. The 16-bit operand at 522DH-522EH is the cell the @PARAM table entry at 54D0H names for the keyword PORT, so @PARAM has already written the typed port number into it. The listing shows the assembled value 0000H, which is what remains when PORT= was not given.
522F
LD A,H 7C
Copy Register H, the high byte of the PORT= value, into Register A ready to be tested against Register L.
5230
OR L B5
OR Register L, the low byte of the PORT= value, into Register A, which already holds the high byte. The Z FLAG is set only when the whole 16-bit PORT= value is zero, that is when the parameter was not supplied.
5231
If the Z FLAG has been set (no PORT= value was given), JUMP to 524AH to leave the built-in driver addressing the memory-mapped printer latch at 37E8H.
5233
INC H 24
INCrement Register H, the high byte of the PORT= value, by 1.
5234
DEC H 25
DECrement Register H, the high byte of the PORT= value, by 1. The increment and decrement together leave Register H unchanged and exist only to set the flags from it: the Z FLAG is set when the high byte is zero, that is when the port number is 255 or less.
5235
Error Path
If the NZ FLAG has been set (the high byte of the PORT= value is not zero, so the port number is above 255 and cannot be encoded in an IN or OUT instruction), JUMP to 5357H to raise parameter error 2CH.
5238
XOR A AF
Set Register A to ZERO and clear all flags. Zero is also the Z80 NOP opcode, and that is what Register A is being loaded for: it is written over the now-surplus third byte of each of the two patched instructions.
5239
LD H,L 65
Copy Register L, the low byte of the PORT= value and therefore the port number itself, into Register H. Register Pair HL is being assembled into an opcode-plus-operand pair that can be stored with a single 16-bit write, low byte first.
523A
LD L,0DBH 2E DB
Load Register L with 0DBH, the Z80 opcode for IN A,(n). Register Pair HL now holds DBH in Register L and the port number in Register H.
523C
Self-Modifying Code
Store Register Pair HL, holding opcode DBH and the port number, at 5553H and 5554H, overwriting the first two bytes of the LD A,(37E8H) that begins the built-in printer driver's status read. The instruction becomes IN A,(port). The patch is applied to the transient image before it is relocated, so the copy that reaches high memory already carries it.
523F
Self-Modifying Code
Store Register A, which holds zero, at 5555H, the third byte of the instruction that was LD A,(37E8H) and is now IN A,(port). The stray byte becomes a NOP so that execution continues correctly at 5556H.
5242
LD L,0D3H 2E D3
Load Register L with 0D3H, the Z80 opcode for OUT (n),A. Register Pair HL now holds D3H in Register L and, still from 5239H, the port number in Register H.
5244
Self-Modifying Code
Store Register Pair HL, holding opcode D3H and the port number, at 554FH and 5550H, overwriting the first two bytes of the LD (37E8H),A that sends a byte to the printer. The instruction becomes OUT (port),A.
5247
Self-Modifying Code
Store Register A, which holds zero, at 5551H, the surplus third byte of the patched output instruction, turning it into a NOP so that the RET at 5552H is still reached.
524A
POP HL E1
Restore Register Pair HL from the stack with 441FH, the address of DFLAG$ in SYS0/SYS, which the EX (SP),HL at 5222H left in that slot.
524B
SET 3,(HL) CB DE
Set bit 3 of the byte addressed by Register Pair HL, which is DFLAG$ at 441FH, marking the printer forms filter as installed. This is the flag the test at 521DH reads on any later attempt, and the flag RESET *PR clears when it unhooks the filter.
524DH - Moving the Format Parameters into the Resident Parameter Block
Five of the parameters live in ordinary two-byte @PARAM cells at 54D9H-54E2H and are copied, one byte each, into the seven-byte parameter block at 5517H-551DH that travels with the resident module. The four switches and SLINE are packed into a single flag byte instead, and XLATE is split into its two halves.
524D
Fetch the low byte of the CHARS= value from its @PARAM cell at 54DDH into Register A. CHARS is the number of printable characters allowed on one line; the cell is pre-set to 0000H, and zero means no line-length limit.
5250
Store the CHARS value (Register A) at 551CH, the sixth byte of the resident parameter block. Once the module has been relocated this byte is reached as (IY+05H) by the line-wrap test at 55A3H.
5253
Fetch the low byte of the MARGIN= value from its @PARAM cell at 54E1H into Register A. MARGIN is the number of spaces emitted before the first character of every line.
5256
Store the MARGIN value (Register A) at 551DH, the seventh and last byte of the resident parameter block, reached as (IY+06H) by the margin test at 5596H.
5259
Fetch the low byte of the INDENT= value from its @PARAM cell at 54DFH into Register A. INDENT is the number of spaces placed at the start of a continuation line produced by the automatic wrap.
525C
Store the INDENT value (Register A) at 551AH, the fourth byte of the resident parameter block, reached as (IY+03H) by the wrap handler at 55B0H.
525F
Fetch the low byte of the SLINE= value into Register A. 5330H is the low half of the 16-bit operand of the LD HL,0000H at 532FH, and that operand is the cell the @PARAM table entry at 54C0H names for the keyword SLINE. SLINE selects the line-numbering convention: 1 for the Model III (67 lines per page, first line numbered 1) and 0, or omitted, for the Model I (66 lines, first line numbered 0).
5262
OR A B7
OR Register A with itself, holding the SLINE value, purely to set the flags. The Z FLAG is set when SLINE is zero or was not supplied.
5263
If the Z FLAG has been set (SLINE is zero or absent), JUMP to 526AH with Register A still holding zero, which is the value the line counter will be reset to at top of form.
5265
DEC A 3D
DECrement Register A, holding the SLINE value, by 1. The result is zero only when SLINE was exactly 1.
5266
Error Path
If the NZ FLAG has been set (SLINE was neither 0 nor 1), JUMP to 5357H to raise parameter error 2CH and abort.
5269
INC A 3C
INCrement Register A back to 1, the accepted SLINE value, having proved by the decrement that it was 1 to begin with.
526A
Self-Modifying Code
Store Register A, holding the SLINE value 0 or 1, at 560CH. That byte is the immediate operand of the LD (IX+04H),01H at 5609H, the instruction that resets the line counter at top of form, so this write decides whether page lines are numbered from 0 (Model I) or from 1 (Model III). Register A is left holding the same value and becomes the seed of the flag byte assembled below.
526D
LD BC,0000H 01 00 00
Self-Modifying Code
Load Register Pair BC with the TAB switch. The 16-bit operand at 526EH-526FH is the cell the @PARAM table entry at 5490H names for TAB, so it holds 0FFFFH when TAB was given and 0000H when it was not.
5270
RLC C CB 01
Rotate Register C, the low half of the TAB switch value, left circularly. The old bit 7 of Register C moves into the CARRY FLAG, so the CARRY FLAG is set when the switch value was 0FFFFH and clear when it was 0000H.
5272
RLA 17
Rotate Register A left through the carry. Register A currently holds the SLINE value in bit 0; the shift moves it to bit 1 and brings the TAB flag in from the CARRY FLAG into bit 0.
5273
LD BC,0000H 01 00 00
Self-Modifying Code
Load Register Pair BC with the FFHARD switch. The 16-bit operand at 5274H-5275H is the cell the @PARAM table entry at 5480H names for FFHARD.
5276
RLC C CB 01
Rotate Register C, the low half of the FFHARD switch value, left circularly, moving its bit 7 into the CARRY FLAG so that the CARRY FLAG now carries the FFHARD setting.
5278
RLA 17
Rotate Register A left through the carry again. SLINE moves to bit 2, TAB to bit 1, and FFHARD arrives in bit 0.
5279
LD BC,0000H 01 00 00
Self-Modifying Code
Load Register Pair BC with the ADDLF switch. The 16-bit operand at 527AH-527BH is the cell the @PARAM table entry at 5470H names for ADDLF.
527C
RLC C CB 01
Rotate Register C, the low half of the ADDLF switch value, left circularly, moving its bit 7 into the CARRY FLAG.
527E
RLA 17
Rotate Register A left through the carry for the third and last time. Register A now holds the finished flag byte: bit 0 ADDLF, bit 1 FFHARD, bit 2 TAB, bit 3 SLINE.
527F
Store the assembled flag byte (Register A) at 551BH, the fifth byte of the resident parameter block. Once relocated it is reached as (IY+04H) and is tested bit by bit at 5588H (TAB), 55C0H (ADDLF) and 55F6H (FFHARD). Bit 3, SLINE, is not tested by the resident code at all; it is preserved so that the FORMS command can read the convention back out and reapply the same plus-one bias to PAGE and LINES.
5282
LD BC,0000H 01 00 00
Self-Modifying Code
Load Register Pair BC with the XLATE character pair. The 16-bit operand at 5283H-5284H is the cell the @PARAM table entry at 54A0H names for XLATE, and holds the value typed as X'aabb', so Register B ends up with aa and Register C with bb.
5285
LD A,B 78
Copy Register B, the high half of the XLATE pair and therefore the character that is to be translated, into Register A.
5286
Store the character to be translated (Register A) at 5518H, the second byte of the resident parameter block, reached as (IY+01H) by the comparison at 556DH.
5289
LD A,C 79
Copy Register C, the low half of the XLATE pair and therefore the replacement character, into Register A.
528A
Store the replacement character (Register A) at 5519H, the third byte of the resident parameter block, reached as (IY+02H) by the substitution at 5573H. With XLATE absent both bytes stay at zero, and the only character that then matches is a null, which is replaced by a null.
528DH - Claiming the High-Memory Block
The resident module is 0125H bytes long, 5505H through 5629H. The first time the filter is installed the space is taken from HIGH$ and the base address is recorded in the resident cell 4DF6H. On any later installation that cell is still set, the same block is re-used, and HIGH$ is left alone. Either way the module's link word is loaded with the address that used to head the high-memory module chain.
528D
Fetch the resident cell at 4DF6H into Register Pair HL. This cell holds the base address of the $FF forms-filter module in high memory, or zero if the module has never been allocated. SYS0/SYS loads it as zero at boot and never writes it itself; the only code that maintains it is this installer.
5290
PUSH HL E5
Save Register Pair HL, the recorded module base (or zero), on the stack while the header is examined through the same pointer.
5291
INC HL 23
INCrement Register Pair HL by 1, stepping from the module base past the 18H signature byte of the module header.
5292
INC HL 23
INCrement Register Pair HL by 1 again. It now addresses module base + 2, the low byte of the header's link word.
5293
LD E,(HL) 5E
Fetch the low byte of the existing module's link word, at module base + 2, into Register E.
5294
INC HL 23
INCrement Register Pair HL by 1 to address module base + 3, the high byte of the link word.
5295
LD D,(HL) 56
Fetch the high byte of the existing module's link word into Register D. Register Pair DE now holds the link word the previous copy of the module carried, which is the address of the next block down the high-memory module chain.
5296
POP HL E1
Restore Register Pair HL from the stack with the recorded module base saved at 5290H.
5297
LD A,H 7C
Copy Register H, the high byte of the recorded module base, into Register A ready to be tested against Register L.
5298
OR L B5
OR Register L, the low byte of the recorded module base, into Register A. The Z FLAG is set only when 4DF6H held zero, meaning no block has been allocated yet.
5299
LD BC,0125H 01 25 01
Load Register Pair BC with 0125H, 293 decimal, the assembled length of the resident module, 5505H through 5629H inclusive. It serves as both the amount to deduct from HIGH$ and the LDIR count at 52DDH. Loading it here does not disturb the flags set by the OR at 5298H.
529C
If the NZ FLAG has been set (4DF6H holds a real address, so the block already exists), JUMP to 52ADH with Register Pair HL still holding that base and Register Pair DE holding the old link word, and re-use the allocation exactly as the DOS manual promises.
529E
LD HL,(4049H) 2A 49 40
Fetch HIGH$, the resident top-of-user-memory pointer at 4049H, into Register Pair HL. Every high-memory module is carved out below it.
52A1
LD E,L 5D
Copy Register L, the low byte of HIGH$, into Register E, beginning a copy of the pointer that will survive the subtraction.
52A2
LD D,H 54
Copy Register H, the high byte of HIGH$, into Register D. Register Pair DE now holds the old value of HIGH$, which becomes the new module's link word: it is the address of whatever already sits below it in the chain.
52A3
XOR A AF
Set Register A to ZERO and clear all flags, in particular the CARRY FLAG, so that the SBC HL,BC that follows behaves as a plain subtraction.
52A4
SBC HL,BC ED 42
SUBtract Register Pair BC (0125H, the module length) from Register Pair HL (the old HIGH$) with the carry known clear. Register Pair HL now holds the address one below where the module will start.
52A6
LD (4049H),HL 22 49 40
Store the reduced value (Register Pair HL) back into HIGH$ at 4049H, permanently reserving the 293 bytes. The DOS manual notes that this memory is not reclaimed by a device reset, only by lowering HIGH$ before a global reset.
52A9
INC HL 23
INCrement Register Pair HL by 1 so that it addresses the first byte of the reserved block, which is where the module header will sit.
52AA
Store the module base (Register Pair HL) into the resident cell 4DF6H, so that a later installation finds it non-zero at 528DH and re-uses this same block instead of taking more memory from HIGH$.
52ADH - Relocating the Module's Internal Addresses
The module is assembled to run at 5505H but will execute wherever it has been placed in high memory. Seventeen instructions inside it carry absolute 16-bit operands that point back into the module; every one of them is named by a DEFW in the table at 54E3H, and each is adjusted by the difference between the destination and 5505H. Relative jumps need no adjustment and do not appear in the table.
52AD
EX DE,HL EB
Exchange Register Pair DE with Register Pair HL. On the fresh-allocation path Register Pair HL held the new module base and Register Pair DE the old HIGH$; on the re-use path Register Pair HL held the recorded base and Register Pair DE the old link word. Either way, after the exchange Register Pair DE holds the destination address and Register Pair HL holds the value that belongs in the module's link word.
52AE
Store Register Pair HL, the address of the next block down the high-memory chain, into 5507H-5508H, the link-word field of the module header in the transient image. The header is copied to high memory with this value already in place, which is how the module joins the chain that the resident module-lookup service walks from HIGH$.
52B1
Point Register Pair HL at 5505H, the first byte of the resident module in the transient image, which is the source address for the coming block move.
52B4
PUSH DE D5
Save Register Pair DE, the destination address of the module in high memory, on the stack across the relocation loop.
52B5
PUSH HL E5
Save Register Pair HL, the source address 5505H, on the stack across the relocation loop.
52B6
PUSH BC C5
Save Register Pair BC, the module length 0125H, on the stack; the relocation loop needs Register Pair BC for the displacement.
52B7
XOR A AF
Set Register A to ZERO and clear all flags, clearing the CARRY FLAG so that the subtraction below is exact.
52B8
EX DE,HL EB
Exchange Register Pair DE with Register Pair HL, putting the destination address into Register Pair HL and the source address 5505H into Register Pair DE ready for the subtraction.
52B9
SBC HL,DE ED 52
SUBtract Register Pair DE (5505H, the assembled origin) from Register Pair HL (the destination in high memory) with the carry clear. Register Pair HL now holds the relocation displacement that must be added to every internal address.
52BB
LD B,H 44
Copy Register H, the high byte of the relocation displacement, into Register B.
52BC
LD C,L 4D
Copy Register L, the low byte of the relocation displacement, into Register C. Register Pair BC now holds the displacement, which is what ADD HL,BC will apply to each operand.
52BD
Point Register Pair IX at 54E3H, the first entry of the relocation table. Each entry is a DEFW holding the address of a 16-bit operand that must be adjusted.
52C1
LD A,11H 3E 11
Load Register A with 11H, 17 decimal, the number of entries in the relocation table. It serves as the loop counter.
52C3
LD L,(IX+00H) DD 6E 00
Loop Start
Fetch the low byte of the current relocation-table entry, addressed by Register Pair IX, into Register L.
52C6
INC IX DD 23
INCrement Register Pair IX by 1 so that it addresses the high byte of the current table entry.
52C8
LD H,(IX+00H) DD 66 00
Fetch the high byte of the current relocation-table entry into Register H. Register Pair HL now holds the address of an operand inside the module image that needs adjusting.
52CB
INC IX DD 23
INCrement Register Pair IX by 1 so that it addresses the first byte of the next table entry.
52CD
LD E,(HL) 5E
Fetch the low byte of the operand that Register Pair HL addresses into Register E.
52CE
INC HL 23
INCrement Register Pair HL by 1 to address the high byte of that operand.
52CF
LD D,(HL) 56
Fetch the high byte of the operand into Register D. Register Pair DE now holds the unadjusted address, which is somewhere in 5505H-5629H.
52D0
EX DE,HL EB
Exchange Register Pair DE with Register Pair HL, putting the unadjusted operand value into Register Pair HL for the addition and the pointer to its high byte into Register Pair DE.
52D1
ADD HL,BC 09
ADD Register Pair BC, the relocation displacement, to Register Pair HL, the unadjusted operand value. Register Pair HL now holds the address the operand must carry once the module is running in high memory.
52D2
EX DE,HL EB
Exchange back: Register Pair DE now holds the adjusted address and Register Pair HL again points at the high byte of the operand in the image.
52D3
LD (HL),D 72
Store Register D, the high byte of the adjusted address, into the high byte of the operand.
52D4
DEC HL 2B
DECrement Register Pair HL by 1 so that it addresses the low byte of the operand again.
52D5
LD (HL),E 73
Store Register E, the low byte of the adjusted address, into the low byte of the operand. That entry is now relocated.
52D6
DEC A 3D
DECrement Register A, the count of relocation-table entries still to be processed, by 1.
52D7
If the NZ FLAG has been set (entries remain), LOOP BACK to 52C3H to relocate the next operand. Loop End
52D9H - Copying the Module and Hooking the Printer DCB
With every internal address adjusted, the 293-byte module is moved into its high-memory home. The printer DCB is then rebuilt in three steps: if it still points at the DOS's own saved printer driver, the module's own driver is substituted; the resulting eight-byte DCB is copied into the module so that the filter has a private path to whatever was there before it; and finally the real DCB is pointed at the module header, which puts the filter in front of everything.
52D9
POP BC C1
Restore Register Pair BC from the stack with 0125H, the module length, saved at 52B6H. It is now the LDIR byte count.
52DA
POP HL E1
Restore Register Pair HL from the stack with 5505H, the source address, saved at 52B5H.
52DB
POP DE D1
Restore Register Pair DE from the stack with the destination address in high memory, saved at 52B4H.
52DC
PUSH DE D5
Save the destination address (Register Pair DE) again, because LDIR advances Register Pair DE past the end of the copy and the base address is still needed.
52DD
LDIR ED B0
Block move. Source Register Pair HL = 5505H, the module header in the transient image; destination Register Pair DE = the reserved block in high memory; count Register Pair BC = 0125H, 293 bytes; direction incrementing. This carries the header, the eight-byte DCB area and its pointer word, the seven-byte parameter block and all 268 bytes of resident code into their permanent home.
52DF
POP BC C1
Restore Register Pair BC from the stack with the module base address in high memory, saved at 52DCH. From here on Register Pair BC is the module base and is used to compute every address inside it.
52E0
POP IX DD E1
Restore Register Pair IX from the stack with the address of the printer Device Control Block, pushed from Register Pair DE by the very first instruction at 5200H. All the DCB manipulation below is indexed through Register Pair IX.
52E2
LD E,(IX+01H) DD 5E 01
Fetch the low byte of the driver address from DCB+01H into Register E. Bytes 1 and 2 of a Device Control Block hold the address of the routine that services the device.
52E5
LD D,(IX+02H) DD 56 02
Fetch the high byte of the driver address from DCB+02H into Register D. Register Pair DE now holds whatever routine the printer DCB currently points at.
52E8
LD HL,(43BCH) 2A BC 43
Fetch PRSV$, the resident cell at 43BCH that holds the DOS's saved printer driver address, into Register Pair HL. SYS0/SYS declares the cell but does not load it, so it is written during system start-up.
52EB
XOR A AF
Set Register A to ZERO and clear all flags, clearing the CARRY FLAG for the exact subtraction that follows.
52EC
SBC HL,DE ED 52
SUBtract Register Pair DE, the printer DCB's current driver address, from Register Pair HL, the saved driver address PRSV$. The Z FLAG is set when the two are the same, which means nothing has taken over the printer and the DOS's own driver is still in place.
52EE
If the NZ FLAG has been set (some other driver is already installed on the printer), JUMP to 52FAH and leave that driver in the chain, so the filter feeds it rather than replacing it.
52F0
LD HL,003AH 21 3A 00
Load Register Pair HL with 003AH, the offset of the built-in printer driver within the module. In the transient image that driver begins at 553FH, and 553FH minus the module origin 5505H is 3AH.
52F3
ADD HL,BC 09
ADD Register Pair BC, the module base in high memory, to Register Pair HL, the 3AH offset. Register Pair HL now holds the run-time address of the module's own printer driver.
52F4
LD (IX+01H),L DD 75 01
Store Register L, the low byte of the module's printer driver address, into DCB+01H of the printer Device Control Block.
52F7
LD (IX+02H),H DD 74 02
Store Register H, the high byte of the module's printer driver address, into DCB+02H. The DCB now names the filter's own driver, which is the only way the PORT= patch can take effect and the reason the missing-printer status test at 5558H applies.
52FA
PUSH BC C5
Save Register Pair BC, the module base in high memory, on the stack across the DCB copy.
52FB
LD HL,0008H 21 08 00
Load Register Pair HL with 0008H, the offset of the module's control block. The header occupies bytes 0 to 7 of the module (signature, displacement, link word, name length and the three characters of "$FF"), so the control block starts at module base + 8, which is 550DH in this listing.
52FE
ADD HL,BC 09
ADD Register Pair BC, the module base, to Register Pair HL, the offset 8. Register Pair HL now holds the run-time address of the module's private eight-byte DCB.
52FF
PUSH HL E5
Save that address (Register Pair HL) on the stack; it is needed again at 530FH after the block move has advanced the working registers.
5300
EX DE,HL EB
Exchange Register Pair DE with Register Pair HL, making Register Pair DE the destination of the coming block move, that is the module's private DCB.
5301
PUSH IX DD E5
Save Register Pair IX, the address of the real printer Device Control Block, on the stack so that it can be pulled into Register Pair HL, which the block move needs as its source.
5303
POP HL E1
Restore that value into Register Pair HL. Register Pair HL now holds the address of the real printer DCB, the source of the copy.
5304
PUSH HL E5
Save the real DCB address (Register Pair HL) on the stack again; it is wanted at 530AH once the copy has finished.
5305
LD BC,0008H 01 08 00
Load Register Pair BC with 0008H, the size of a Device Control Block, as the block-move count.
5308
LDIR ED B0
Block move. Source Register Pair HL = the real printer DCB; destination Register Pair DE = the module's private DCB at module base + 8; count Register Pair BC = 8 bytes; direction incrementing. The copy is taken after the driver substitution above, so it names either the DOS printer driver that was there before, or the filter's own driver at module base + 3AH. Every pass-through call the resident filter makes at 5524H, 552DH and 5537H uses this copy.
530A
POP HL E1
Restore Register Pair HL from the stack with the address of the real printer Device Control Block, saved at 5304H.
530B
EX DE,HL EB
Exchange Register Pair DE with Register Pair HL. Register Pair DE now holds the real DCB address, and Register Pair HL holds the address LDIR left behind, which is module base + 16, the two-byte slot immediately after the private DCB.
530C
LD (HL),E 73
Store Register E, the low byte of the real printer DCB address, at module base + 16. In this listing that slot is 5515H.
530D
INC HL 23
INCrement Register Pair HL by 1 to address module base + 17, which is 5516H in this listing.
530E
LD (HL),D 72
Store Register D, the high byte of the real printer DCB address. The module now carries a pointer back to the DCB whose working bytes hold the page counters, and it is this word that the FORMS command follows when it edits the filter's parameters from outside.
530F
POP BC C1
Restore Register Pair BC from the stack with the address of the module's private eight-byte DCB, module base + 8, saved at 52FFH. Register Pair BC keeps that value to the end of the program and is what the spooler-linkage block at 5351H plants.
5310
POP HL E1
Restore Register Pair HL from the stack with the module base address in high memory, saved at 52FAH.
5311
LD (IX+01H),L DD 75 01
Store Register L, the low byte of the module base, into DCB+01H of the real printer Device Control Block.
5314
LD (IX+02H),H DD 74 02
Store Register H, the high byte of the module base, into DCB+02H. The printer DCB now points at the module header itself, whose first two bytes are a JR that jumps to the filter entry: the header's signature byte 18H is simultaneously the opcode of that jump. From this instruction onward every character sent to the device passes through the filter.
5317H - Seeding the Page Counters in the Printer DCB
The three page-geometry values do not live in the module. They are kept in the working-storage bytes of the printer Device Control Block, DCB+03H, DCB+04H and DCB+05H, which are reachable from a command overlay that only knows the module's name. Both PAGE and LINES are stored with one added; the SLINE convention then decides whether that bias survives.
5317
Fetch the low byte of the PAGE= value from its @PARAM cell at 54DBH into Register A. The cell is pre-set to 0042H, 66 decimal, the standard physical page length in lines.
531A
INC A 3C
INCrement Register A, the physical page length, by 1. The plus-one bias makes the stored value the number of the line that is one past the end of the page, which is the form the page-advance arithmetic at 55ECH wants.
531B
LD (IX+03H),A DD 77 03
Store the biased page length (Register A) into DCB+03H of the printer Device Control Block, the first of its three working-storage bytes.
531E
LD (IX+04H),01H DD 36 04 01
Store 01H into DCB+04H, the current line number within the page. Printing therefore starts on line 1 under the Model III convention; the decrement at 5339H turns it into line 0 under the Model I convention.
5322
Fetch the low byte of the LINES= value from its @PARAM cell at 54D9H into Register A. The cell is pre-set to 0FFFFH, so the low byte is 0FFH when LINES was not supplied.
5325
INC A 3C
INCrement Register A, the printed-lines-per-page value, by 1, applying the same plus-one bias used for PAGE. When LINES was absent the byte was 0FFH and this wraps it to zero.
5326
AND A A7
AND Register A with itself purely to set the flags. The Z FLAG is set only in the wrapped case, that is only when LINES was not supplied.
5327
If the NZ FLAG has been set (a real LINES= value was given), JUMP to 532CH to store it as it stands.
5329
LD A,(IX+03H) DD 7E 03
Fetch DCB+03H, the biased physical page length just written at 531BH, into Register A. With no LINES= parameter the printed area fills the whole page, which is what the DOS manual states as the default.
532C
LD (IX+05H),A DD 77 05
Store the biased printed-lines-per-page value (Register A) into DCB+05H of the printer Device Control Block, the third working-storage byte. It is the value the line counter is compared against at 55E8H to decide when the printed area is full.
532F
LD HL,0000H 21 00 00
Self-Modifying Code
Load Register Pair HL with the SLINE= value. The 16-bit operand at 5330H-5331H is the cell the @PARAM table entry at 54C0H names for SLINE, and the low half of it is the byte already read at 525FH. The listing shows the assembled value 0000H.
5332
LD A,H 7C
Copy Register H, the high byte of the SLINE value, into Register A ready to be tested against Register L.
5333
OR L B5
OR Register L, the low byte of the SLINE value, into Register A. The Z FLAG is set when SLINE is zero or was not supplied, which selects the Model I convention.
5334
If the NZ FLAG has been set (SLINE=1, the Model III convention), JUMP to 533FH leaving the three counters at 67, 1 and 67, which is exactly what the DOS manual describes for that setting.
5336
DEC (IX+03H) DD 35 03
DECrement DCB+03H, the biased physical page length, by 1, returning it to 66 for a standard page. This removes the plus-one bias for the Model I convention.
5339
DEC (IX+04H) DD 35 04
DECrement DCB+04H, the current line number, by 1, so the first line of the page is numbered 0 rather than 1.
533C
DEC (IX+05H) DD 35 05
DECrement DCB+05H, the biased printed-lines-per-page value, by 1, giving 66 for a standard page. The three counters now read 66, 0 and 66, the Model I convention, matching the 00H the SLINE handling planted in the line-counter reset operand at 560CH.
533FH - Spooler Linkage and Exit
One last hook. The print spooler, when it is resident, keeps its own copy of the address of the DCB its despooler sends characters to, because the despooler runs as an interrupt task and calls the printer driver directly rather than going through the device chain. If the filter has just been inserted in front of that device, the despooler has to be pointed at the filter's private DCB instead of the real one, so that both present the same control block to the printer driver. The block is skipped entirely when the spooler is not installed, and the installer then returns to the DOS through @EXIT.
533F
Point Register Pair HL at DFLAG$, the resident device-flag byte at 441FH in SYS0/SYS, for the second time in this program. Bit 0 of DFLAG$ records whether the print spooler is installed.
5342
BIT 0,(HL) CB 46
Test bit 0 of the byte addressed by Register Pair HL, which is DFLAG$ at 441FH. The Z FLAG is set when the spooler is not present.
5344
If the Z FLAG has been set (no spooler is installed), JUMP to 5354H and exit without touching the spooler's control block.
5346
Fetch the resident cell at 4DF0H into Register Pair HL. It is the spooler's counterpart of the filter's own cell at 4DF6H, but it is not laid out the same way: 4DF6H holds the base of the $FF module, while 4DF0H holds the address of the spooler module's CONTROL BLOCK, that is its base plus 0AH, because the name "SPOOL" is five characters long. SYS0/SYS loads both cells as zero at boot and never writes either of them.
5349
LD DE,0008H 11 08 00
Load Register Pair DE with 0008H, the offset of the field wanted within the spooler's control block. Register Pair HL already addresses that control block, so this is an offset into it and not the base-to-control-block step used at 52FBH.
534C
ADD HL,DE 19
ADD Register Pair DE, the offset 8, to Register Pair HL, the spooler's control block address. Register Pair HL now addresses the control block field at offset 8, which the spooler filled in when it installed itself with the address of the cell that holds its despooler's output DCB pointer.
534D
LD A,(HL) 7E
Fetch the low byte of the field at control block + 8 into Register A.
534E
INC HL 23
INCrement Register Pair HL by 1 to address the high byte of the word.
534F
LD H,(HL) 66
Fetch the high byte of the field directly into Register H, overwriting the pointer as it is consumed.
5350
LD L,A 6F
Copy Register A, the low byte fetched at 534DH, into Register L. Register Pair HL now addresses the two-byte cell inside the spooler module that holds the DCB pointer its despooler loads into Register Pair IX before calling the printer driver. The spooler seeded that cell with the address of the device DCB it found when it installed.
5351
LD (HL),C 71
Store Register C, the low byte of the address Register Pair BC has held since 530FH, which is the filter's own private eight-byte DCB at module base + 8, into that cell.
5352
INC HL 23
INCrement Register Pair HL by 1.
5353
LD (HL),B 70
Store Register B, the high byte of the filter's private DCB address. The despooler now hands the printer driver the same Device Control Block the filter itself uses, rather than the real device DCB whose driver field has just been redirected to the filter. The despooler still calls the printer driver directly, at the address it captured when it installed, so queued characters are not passed through the filter a second time.
5354
JUMP to @EXIT, the resident vector at 402DH in SYS0/SYS, which returns to the DOS command level. The installer's work is finished; the transient image at 5200H-5504H is abandoned and the next program loaded will overwrite it.
5357H - Parameter Error Path
Reached from the three places where a parameter cannot be accepted: an unrecognised keyword at 5229H, a PORT= value above 255 at 5235H, and an SLINE= value other than 0 or 1 at 5266H.
5357
LD A,2CH 3E 2C
Load Register A with 2CH, 44 decimal, the DOS error number for a parameter error. Bits 0 to 5 of the byte handed to @ERROR carry the error number.
5359
OR 0C0H F6 C0
OR 0C0H into Register A, setting bits 6 and 7 alongside the error number. Bit 6 asks @ERROR for the message text only, without the "Error code =" preamble; bit 7 asks it to return to the caller rather than abort on its own.
535B
GOSUB to the resident @ERROR vector at 4409H in SYS0/SYS, which loads SYS4/SYS and displays the text for the error number in Register A.
535E
JUMP to 5372H to abort, joining the same exit the four message paths use.
5360H - Message Selection and Abort
Four abort messages share one @LOGOT call and one abort. Each entry point loads Register Pair HL with its own message address, and a single DDH byte sits between them: entered at 5360H, 5364H or 5368H the following 21 nn nn reads as LD IX rather than LD HL, so Register Pair HL survives untouched all the way to the call. Entering at 5364H, 5368H or 536CH skips the DDH and the same three bytes load Register Pair HL directly.
5360
Point Register Pair HL at the message Device not active! at 53DEH. Reached from 520DH when bit 3 of the DCB type byte says the device has been set to NIL.
5363
DEFB 0DDH DD
Skip Byte
A bare DDH index-register prefix. On the fall-through from 5360H it binds to the three bytes at 5364H and turns that LD HL into LD IX,53F1H, which leaves Register Pair HL holding 53DEH. Register Pair IX is dead at this point in the program, so the side effect costs nothing.
5364
Point Register Pair HL at the message Not an output device! at 53F1H. Reached from 5217H when bit 1 of the DCB type byte says the device does not accept output.
5367
DEFB 0DDH DD
Skip Byte
The same prefix trick. Falling through from 5364H it turns the three bytes at 5368H into LD IX,5407H, preserving whatever Register Pair HL already holds.
5368
Point Register Pair HL at the message PR/FLT already active! at 5407H. Reached from 521FH when DFLAG$ bit 3 shows the filter is already installed.
536B
DEFB 0DDH DD
Skip Byte
The last of the three prefixes. Falling through from 5368H it turns the three bytes at 536CH into LD IX,541EH.
536C
Point Register Pair HL at the message Device is routed! at 541EH. Reached from 5212H when bit 4 of the DCB type byte says the device has been routed to a disk file.
536F
GOSUB to the resident @LOGOT vector at 447BH in SYS0/SYS, which sends the 0DH-terminated string addressed by Register Pair HL to the video display and, when a Job Control Language job is running, to the job log as well.
5372
JUMP to @ABORT, the resident vector at 4030H in SYS0/SYS, ending the program with an error indication. Nothing has been installed: DFLAG$ bit 3 is only set at 524BH, after every one of these tests has been passed.
5375H - Sign-On Banner
The banner @DSPLY writes at 5207H, before any validation is done, so it appears even when the installation is about to be refused. It is data, not code, and it is transient: it lives at 5375H in the load image and is never copied to high memory.
5375
DEFM 'PR/FLT - Generalized Printer Filter - Version 5.3.1' 50 52 2F 46 4C 54 20 2D 20 47 65 6E 65 72 61 6C 69 7A 65 64 20 50 72 69 6E 74 65 72 20 46 69 6C 74 65 72 20 2D 20 56 65 72 73 69 6F 6E 20 35 2E 33 2E 31
The product line of the banner. PR/FLT identifies itself as the generalized printer filter and states the DOS release it belongs to, 5.3.1. Register Pair HL is loaded with this address at 5204H.
53A8
DEFB 0AH 0A
A line feed, ending the product line. The resident video driver treats 0AH as a move to the start of the next line.
53A9
DEFM 'Copyright 1991, MISOSYS, Inc., All rights reserved' 43 6F 70 79 72 69 67 68 74 20 31 39 39 31 2C 20 4D 49 53 4F 53 59 53 2C 20 49 6E 63 2E 2C 20 20 41 6C 6C 20 72 69 67 68 74 73 20 72 65 73 65 72 76 65 64
The MISOSYS copyright line. The double space before All is present in the file and is reproduced here as it stands.
53DC
DEFB 0AH, 0DH 0A 0D
A line feed to close the copyright line and the 0DH that terminates the string for @DSPLY at 4467H. @DSPLY stops on 0DH and does not send it, so the banner ends with the cursor at the start of a fresh line.
53DEH - Abort Message Text
The four refusal messages, each ended by 0DH for the @LOGOT call at 536FH. Every one of them is followed immediately by an abort through @ABORT at 4030H, so the filter is never partly installed.
53DE
DEFM 'Device not active!' 44 65 76 69 63 65 20 6E 6F 74 20 61 63 74 69 76 65 21
Issued from 5360H when bit 3 of the DCB type byte shows that the device has been set to NIL. There is no driver behind it to filter into.
53F0
DEFB 0DH 0D
String terminator for @LOGOT.
53F1
DEFM 'Not an output device!' 4E 6F 74 20 61 6E 20 6F 75 74 70 75 74 20 64 65 76 69 63 65 21
Issued from 5364H when bit 1 of the DCB type byte, the output capability bit, is clear.
5406
DEFB 0DH 0D
String terminator for @LOGOT.
5407
DEFM 'PR/FLT already active!' 50 52 2F 46 4C 54 20 61 6C 72 65 61 64 79 20 61 63 74 69 76 65 21
Issued from 5368H when DFLAG$ at 441FH already has bit 3 set. A second installation would chain the filter to its own output.
541D
DEFB 0DH 0D
String terminator for @LOGOT.
541E
DEFM 'Device is routed!' 44 65 76 69 63 65 20 69 73 20 72 6F 75 74 65 64 21
Issued from 536CH when bit 4 of the DCB type byte shows the device has been routed to a disk file.
542F
DEFB 0DH 0D
String terminator for @LOGOT.
5430H - @PARAM Keyword Table
The table Register Pair DE points at when @PARAM is called at 5226H. Every entry is eight bytes: a six-character keyword padded with blanks, then the address of the two-byte cell that is to receive the value. Every keyword appears twice, spelled out and abbreviated, and both forms name the same cell. A 00H byte at 54D8H ends the table. Six of the cells are ordinary storage at 54D9H-54E2H; the other five are 16-bit operands inside the installer itself, which is how a parameter turns directly into an instruction operand.
5430
DEFM 'LINES ' 4C 49 4E 45 53 20
Keyword field of the LINES entry, six characters padded with blanks. @PARAM matches the text the user typed against these six bytes.
5436
Address of the cell that receives the number of printed lines per page.
5438
DEFM 'L ' 4C 20 20 20 20 20
Keyword field of the abbreviated form L of LINES, six characters padded with blanks. @PARAM matches the text the user typed against these six bytes.
543E
Address of the cell that receives the number of printed lines per page.
5440
DEFM 'PAGE ' 50 41 47 45 20 20
Keyword field of the PAGE entry, six characters padded with blanks. @PARAM matches the text the user typed against these six bytes.
5446
Address of the cell that receives the physical page length in lines.
5448
DEFM 'P ' 50 20 20 20 20 20
Keyword field of the abbreviated form P of PAGE, six characters padded with blanks. @PARAM matches the text the user typed against these six bytes.
544E
Address of the cell that receives the physical page length in lines.
5450
DEFM 'CHARS ' 43 48 41 52 53 20
Keyword field of the CHARS entry, six characters padded with blanks. @PARAM matches the text the user typed against these six bytes.
5456
Address of the cell that receives the number of printable characters per line.
5458
DEFM 'C ' 43 20 20 20 20 20
Keyword field of the abbreviated form C of CHARS, six characters padded with blanks. @PARAM matches the text the user typed against these six bytes.
545E
Address of the cell that receives the number of printable characters per line.
5460
DEFM 'INDENT' 49 4E 44 45 4E 54
Keyword field of the INDENT entry, six characters padded with blanks. @PARAM matches the text the user typed against these six bytes.
5466
Address of the cell that receives the indent applied to a wrapped continuation line.
5468
DEFM 'I ' 49 20 20 20 20 20
Keyword field of the abbreviated form I of INDENT, six characters padded with blanks. @PARAM matches the text the user typed against these six bytes.
546E
Address of the cell that receives the indent applied to a wrapped continuation line.
5470
DEFM 'ADDLF ' 41 44 44 4C 46 20
Keyword field of the ADDLF entry, six characters padded with blanks. @PARAM matches the text the user typed against these six bytes.
5476
Address of the cell that receives the ADDLF switch, the operand of the LD BC at 5279H.
5478
DEFM 'A ' 41 20 20 20 20 20
Keyword field of the abbreviated form A of ADDLF, six characters padded with blanks. @PARAM matches the text the user typed against these six bytes.
547E
Address of the cell that receives the ADDLF switch, the operand of the LD BC at 5279H.
5480
DEFM 'FFHARD' 46 46 48 41 52 44
Keyword field of the FFHARD entry, six characters padded with blanks. @PARAM matches the text the user typed against these six bytes.
5486
Address of the cell that receives the FFHARD switch, the operand of the LD BC at 5273H.
5488
DEFM 'F ' 46 20 20 20 20 20
Keyword field of the abbreviated form F of FFHARD, six characters padded with blanks. @PARAM matches the text the user typed against these six bytes.
548E
Address of the cell that receives the FFHARD switch, the operand of the LD BC at 5273H.
5490
DEFM 'TAB ' 54 41 42 20 20 20
Keyword field of the TAB entry, six characters padded with blanks. @PARAM matches the text the user typed against these six bytes.
5496
Address of the cell that receives the TAB switch, the operand of the LD BC at 526DH.
5498
DEFM 'T ' 54 20 20 20 20 20
Keyword field of the abbreviated form T of TAB, six characters padded with blanks. @PARAM matches the text the user typed against these six bytes.
549E
Address of the cell that receives the TAB switch, the operand of the LD BC at 526DH.
54A0
DEFM 'XLATE ' 58 4C 41 54 45 20
Keyword field of the XLATE entry, six characters padded with blanks. @PARAM matches the text the user typed against these six bytes.
54A6
Address of the cell that receives the XLATE character pair, the operand of the LD BC at 5282H.
54A8
DEFM 'X ' 58 20 20 20 20 20
Keyword field of the abbreviated form X of XLATE, six characters padded with blanks. @PARAM matches the text the user typed against these six bytes.
54AE
Address of the cell that receives the XLATE character pair, the operand of the LD BC at 5282H.
54B0
DEFM 'MARGIN' 4D 41 52 47 49 4E
Keyword field of the MARGIN entry, six characters padded with blanks. @PARAM matches the text the user typed against these six bytes.
54B6
Address of the cell that receives the width of the left margin in spaces.
54B8
DEFM 'M ' 4D 20 20 20 20 20
Keyword field of the abbreviated form M of MARGIN, six characters padded with blanks. @PARAM matches the text the user typed against these six bytes.
54BE
Address of the cell that receives the width of the left margin in spaces.
54C0
DEFM 'SLINE ' 53 4C 49 4E 45 20
Keyword field of the SLINE entry, six characters padded with blanks. @PARAM matches the text the user typed against these six bytes.
54C6
Address of the cell that receives the line-numbering convention, the operand of the LD HL at 532FH.
54C8
DEFM 'SL ' 53 4C 20 20 20 20
Keyword field of the abbreviated form SL of SLINE, six characters padded with blanks. @PARAM matches the text the user typed against these six bytes.
54CE
Address of the cell that receives the line-numbering convention, the operand of the LD HL at 532FH.
54D0
DEFM 'PORT ' 50 4F 52 54 20 20
Keyword field of the PORT entry, six characters padded with blanks. @PARAM matches the text the user typed against these six bytes.
54D6
Address of the cell that receives the printer port number, the operand of the LD HL at 522CH.
54D8
DEFB 00H 00
End of table. @PARAM stops scanning when it reads a zero in the first byte of an entry.
54D9H - @PARAM Value Cells
The five parameters whose values are not instruction operands are held here. @PARAM writes the whole 16 bits, but the installer only ever reads the low byte of each, so a value above 255 is silently truncated. The assembled contents shown are the pre-set defaults.
54D9
DEFW 0FFFFH FF FF
LINES, the number of printed lines on each page. The pre-set 0FFFFH is a sentinel: the INC A at 5325H turns its low byte 0FFH into zero, which is how the installer detects that LINES was not supplied and copies the PAGE value instead.
54DB
DEFW 0042H 42 00
PAGE, the physical page length in lines. Pre-set to 0042H, 66 decimal, the standard form length. Read at 5317H, biased by one, and stored in DCB+03H.
54DD
DEFW 0000H 00 00
CHARS, the number of printable characters allowed on a line. Read at 524DH and stored at 551CH. Zero, the pre-set value, disables the wrap test at 55A7H altogether.
54DF
DEFW 0000H 00 00
INDENT, the number of spaces placed at the start of a continuation line after an automatic wrap. Read at 5259H and stored at 551AH.
54E1
DEFW 0000H 00 00
MARGIN, the number of spaces emitted before the first character of every line. Read at 5253H and stored at 551DH.
54E3H - Relocation Table
Seventeen DEFWs, each holding the address of a 16-bit operand inside the module that points back into the module and therefore has to be adjusted when the module moves. The loop at 52C3H reads each entry, fetches the operand it names, adds the relocation displacement and writes it back. Relative jumps and the DDH and FDH indexed instructions carry no absolute addresses and are absent from the table.
54E3
DEFW 552EH 2E 55
Entry 1. Names the DCB operand of the LD DE at 552DH, the pass-through path for an input request.
54E5
DEFW 5525H 25 55
Entry 2. Names the DCB operand of the LD DE at 5524H, the pass-through path for a control request.
54E7
DEFW 5538H 38 55
Entry 3. Names the DCB operand of the LD DE at 5537H, the pass-through path for an output request.
54E9
DEFW 554AH 4A 55
Entry 4. Names the operand of the CALL at 5549H, the printer status test.
54EB
DEFW 5562H 62 55
Entry 5. Names the operand of the LD IY at 5560H, the base of the resident parameter block.
54ED
DEFW 5566H 66 55
Entry 6. Names the operand of the CALL at 5565H, the character filter.
54EF
DEFW 559BH 9B 55
Entry 7. Names the operand of the CALL NZ at 559AH, the left-margin routine.
54F1
DEFW 559EH 9E 55
Entry 8. Names the operand of the CALL at 559DH, the send-onward routine.
54F3
DEFW 55AEH AE 55
Entry 9. Names the operand of the CALL at 55ADH, the new-line and page-accounting routine.
54F5
DEFW 55BAH BA 55
Entry 10. Names the operand of the CALL at 55B9H, the print-a-character routine used to emit the indent.
54F7
DEFW 55C9H C9 55
Entry 11. Names the operand of the CALL at 55C8H, the send-onward routine on the ADDLF path.
54F9
DEFW 55D8H D8 55
Entry 12. Names the operand of the CALL at 55D7H, the send-onward routine on the plain carriage-return path.
54FB
DEFW 55E0H E0 55
Entry 13. Names the operand of the CALL at 55DFH, the carriage-return and line-feed routine.
54FD
DEFW 55FFH FF 55
Entry 14. Names the operand of the CALL at 55FEH, the send-onward routine used for a hard form feed.
54FF
DEFW 5604H 04 56
Entry 15. Names the operand of the CALL at 5603H, the carriage-return and line-feed routine used to space to top of form.
5501
DEFW 5624H 24 56
Entry 16. Names the operand of the CALL at 5623H, the send-onward routine used to emit a margin space.
5503
DEFW 5586H 86 55
Entry 17. Names the operand of the JP Z at 5585H, the top-of-page reset reached by a 06H character.
5505H - Resident Module Header
The eight-byte header every LDOS high-memory module carries. Its first byte doubles as the opcode of the entry vector, so the address the device DCB is pointed at is the header itself. The resident module-lookup service reached by RST 28H request code 0BCH walks the chain of these headers down from HIGH$, matches the name, and hands back the address of the bytes that follow it.
5505
DEFB 18H, 17H 18 17
The 18H signature byte, which is also the opcode of a relative jump, followed by the displacement 17H. Read as an instruction the pair is JR 551EH: 5507H plus 17H is 551EH, the filter entry. Because the DCB is pointed at this byte, the module identifies itself and dispatches with the same two bytes.
5507
DEFW 0000H 00 00
The link word, the address of the next module down the high-memory chain. Assembled as zero and overwritten at 52AEH with the previous chain head, which is either the old value of HIGH$ or the link word the earlier copy of this module carried.
5509
DEFB 03H 03
The name length. Only the low nibble is significant to the lookup service; here it is three.
550A
DEFM '$FF' 24 46 46
The module name, $FF, standing for forms filter. This is the name the FORMS command in SYS7/SYS overlay 12 passes to the module-lookup service to find the resident filter and edit its parameters.
550DH - The Module Control Block (Not Loaded)
Ten bytes the load records deliberately skip. They are declared storage, not initialised data, and the installer fills them in at 52FAH-530EH just before the DCB is hooked. Everything from 5505H to 5629H is copied to high memory as one block, so these ten bytes travel with the module. In the LDOS module convention the bytes following the name are the control block, which makes this area exactly what the lookup service hands back to a caller.
550D-5514
DEFS 8
The module's private copy of the printer Device Control Block, taken from the real one by the LDIR at 5308H. Byte 0 is the device type, bytes 1 and 2 the address of the driver that was serving the printer before the filter was inserted, bytes 3 to 5 the working storage and bytes 6 and 7 the device name. Register Pair DE is loaded with this address at 5524H, 552DH and 5537H, so every pass-through the filter performs goes to the previous driver and not back to itself.
5515-5516
DEFS 2
A pointer to the real printer Device Control Block, written at 530CH and 530EH. This is the word the FORMS command follows, after the module-lookup service has handed it the control block address, to reach DCB+03H, DCB+04H and DCB+05H, the page length, current line and printed-lines counters.
Unloaded Gap
The LDOS load records cover 5200H-550CH and then resume at 5517H. The ten bytes in between carry no file data and are shown here as reserved storage.
5517H - The Resident Parameter Block
Seven bytes of formatting state that travel with the module and are addressed through Register Pair IY, which the output entry at 5560H points here. Six of them are written by the installer at 524DH-528CH; the first is a running counter maintained by the filter itself. Together with the three working bytes of the printer DCB these seven bytes are the ten-byte structure the FORMS command reads and rewrites.
5517
DEFB 00H 00
(IY+00H) Column counter. The number of printable characters already emitted on the current line. Incremented at 55A0H, zeroed at 55DAH, and tested at 55CDH and 561BH. Margin spaces are emitted without being counted, so this is a count of the printable field only. Assembled as zero, which is the correct starting state.
5518
DEFB 00H 00
(IY+01H) The XLATE source character, the aa half of X'aabb'. Written at 5286H. Compared against every outgoing character at 5570H. Zero when XLATE was not given, in which case only a null matches.
5519
DEFB 00H 00
(IY+02H) The XLATE replacement character, the bb half of X'aabb'. Written at 528AH and substituted at 5573H.
551A
DEFB 00H 00
(IY+03H) INDENT. The number of spaces placed at the start of a continuation line after an automatic wrap. Written at 525CH, read at 55B0H.
551B
DEFB 00H 00
(IY+04H) The flag byte. Bit 0 ADDLF, tested at 55C0H; bit 1 FFHARD, tested at 55F6H; bit 2 TAB, tested at 5588H; bit 3 SLINE, which the resident code never reads and which is preserved only so that the FORMS command can recover the line-numbering convention. Written at 527FH.
551C
DEFB 00H 00
(IY+05H) CHARS. The number of printable characters allowed on a line. Written at 5250H, read at 55A3H. Zero disables the wrap.
551D
DEFB 00H 00
(IY+06H) MARGIN. The number of spaces emitted whenever the column counter is zero. Written at 5256H, read at 5596H.
551EH - Resident Filter Entry
Everything from here to 5629H runs in high memory, not at the addresses shown; the listing keeps the assembled 5505H origin, and the relocation table at 54E3H names every operand that has to be corrected for the difference. The entry is reached through the JR that the module header's 18H signature byte forms, which is where the printer DCB now points. The Model I ROM reaches a device driver through the central I/O dispatcher at 03C2H, which sets Register Pair IX to the Device Control Block, puts the data or control byte in Register C, and selects the operation with the flags: CARRY set for an input request from $GET at 0013H, ZERO set for an output request from $PUT at 001BH, and neither for a control request from $CTL at 0023H. Register Pair IX therefore addresses the printer DCB throughout, which is how the page counters in DCB+03H to DCB+05H are reached.
551E
If the CARRY FLAG has been set (the caller entered through $GET at 0013H and wants a byte read from the device), JUMP to 552CH to hand the request straight on to the driver behind the filter. There is nothing to format on the way in.
5520
If the Z FLAG has been set (the caller entered through $PUT at 001BH with a byte to print in Register C), JUMP to 555EH, the only path that does any filtering.
5522
LD A,C 79
Neither flag was set, so this is a control request from $CTL at 0023H. Copy Register C, which holds the control byte the caller supplied, into Register A, because the ROM control entry expects it there.
5523
PUSH DE D5
Save Register Pair DE, which the caller left holding the address of the printer Device Control Block, so that it can be restored before returning.
5524
Point Register Pair DE at the module's own eight-byte copy of the printer DCB, at module base + 8. The operand is one of the seventeen the relocation table adjusts, entry 2 at 54E5H. Passing this DCB rather than the real one is what makes the filter transparent: the call goes to the driver that was serving the printer before the filter was installed.
5527
GOSUB to the ROM $CTL entry at 0023H, which loads Register B with the device-type code 04H and enters the central I/O dispatcher, sending the control byte in Register A to the device whose DCB is addressed by Register Pair DE.
552A
POP DE D1
Restore Register Pair DE with the caller's original DCB address, saved at 5523H.
552B
RET C9
Return to the caller with whatever status the underlying driver left in Register A and the flags.
552C
PUSH DE D5
Input path. Save Register Pair DE, the caller's printer DCB address, across the pass-through call.
552D
Point Register Pair DE at the module's own copy of the printer DCB at module base + 8. This operand is relocation table entry 1 at 54E3H.
5530
GOSUB to the ROM $GET entry at 0013H, which loads Register B with the device-type code 01H and enters the central I/O dispatcher to read a byte from the device whose DCB is addressed by Register Pair DE. The byte comes back in Register A.
5533
POP DE D1
Restore Register Pair DE with the caller's original DCB address, saved at 552CH.
5534
RET C9
Return to the caller with the byte in Register A exactly as the underlying driver produced it.
5535H - Send a Character Onward
The single exit every formatting decision funnels through. It takes the byte in Register C, which is where the filter keeps the character it is working on, and passes it to whatever driver the module's private DCB names. Ten of the seventeen relocation entries exist because of the calls to this routine and to the DCB address it uses.
5535
LD A,C 79
Copy Register C, holding the character that is to be printed, into Register A, which is where the ROM output entry expects the data byte.
5536
PUSH DE D5
Save Register Pair DE across the call. The caller's DCB address is still in it and the filter's own routines rely on it being intact when they return.
5537
Point Register Pair DE at the module's own eight-byte copy of the printer DCB, at module base + 8, whose driver address field names either the DOS printer driver that was in place before the filter, or the filter's own driver at 553FH when the DCB was still pointing at PRSV$. This operand is relocation table entry 3 at 54E7H.
553A
GOSUB to the ROM $PUT entry at 001BH, which loads Register B with the device-type code 02H and enters the central I/O dispatcher, sending the byte in Register A to the device whose DCB is addressed by Register Pair DE.
553D
POP DE D1
Restore Register Pair DE with the value saved at 5536H.
553E
RET C9
Return to the filter routine that asked for the character to be sent.
553FH - The Built-In Model I Printer Driver
A complete printer driver, installed into the printer DCB at 52F4H whenever that DCB was still pointing at PRSV$, the DOS's own saved driver. It is reached exactly as any driver is, with Register Pair IX addressing the DCB, Register C holding the byte, and the flags selecting the operation. Its two accesses to the Model I printer latch at 37E8H are the instructions the PORT= parameter rewrites into port instructions at 523CH and 5244H, which is why PORT= exists at all: without this driver in the DCB there would be nothing to patch.
553F
If the Z FLAG has been set (an output request, Register C holding the byte to print), JUMP to 5549H to wait for the printer and send it.
5541
If the CARRY FLAG has been set (an input request), JUMP to 5547H. A line printer has nothing to read, so the request is answered with zero.
5543
LD A,C 79
A control request. Copy Register C, the control byte the caller supplied, into Register A to be tested.
5544
OR A B7
OR Register A with itself, holding the control byte, purely to set the flags. The Z FLAG is set when the control byte is zero, which is the request for printer status.
5545
If the Z FLAG has been set (a control byte of zero, meaning report the printer status), JUMP to 5553H, whose RET returns the status directly to the caller with the Z FLAG set when the printer is ready.
5547
XOR A AF
Set Register A to ZERO and clear all flags. Zero with the Z FLAG set is the "nothing to report, device ready" answer this driver gives to an input request or to any control byte other than zero.
5548
RET C9
Return to the caller.
5549
Loop Start
GOSUB to the status test at 5553H, which reads the printer status and returns with the Z FLAG set when the printer will accept a character. The operand is relocation table entry 4 at 54E9H.
554C
If the NZ FLAG has been set (the printer is busy, out of paper or deselected), LOOP BACK to 5549H and test again. The DOS manual describes this as waiting until printer capabilities have been re-established; the status test itself is what stops a machine with no printer connected from spinning here forever. Loop End
554E
LD A,C 79
Copy Register C, holding the character to be printed, into Register A ready to be written to the printer.
554F
LD (37E8H),A 32 E8 37
Self-Modifying Code
Store Register A, the character being printed, into the Model I memory-mapped printer data latch at 37E8H. When PORT= was given, 5244H replaced the first two bytes of this instruction with OUT (port),A and 5247H turned the third byte into a NOP, so the same character goes to a genuine Z80 output port instead.
5552
RET C9
Return to the caller; the character has been accepted by the printer interface.
5553
LD A,(37E8H) 3A E8 37
Self-Modifying Code
Fetch the Model I printer status from the memory-mapped latch at 37E8H into Register A. Reading the same address that is written for data returns the interface status in the four high bits. When PORT= was given, 523CH replaced the first two bytes of this instruction with IN A,(port) and 523FH turned the third byte into a NOP.
5556
AND 0F0H E6 F0
AND 0F0H with Register A, the printer status just read, keeping only the four status bits and discarding the low nibble, which carries no information on this interface.
5558
CP 0F0H FE F0
Compare Register A, the masked printer status, against 0F0H. All four status bits set is what an unconnected printer interface reads back, because nothing is pulling the lines down. If Register A equals 0F0H the Z FLAG is set.
555A
RET Z C8
If the Z FLAG has been set (the status read back as all ones, so no printer is attached), return immediately with the Z FLAG still set, that is reporting the device as ready. This is the whole of the DOS manual's promise that a print command will not lock the machine up when the printer is not connected: the wait loop at 5549H sees a ready status and the characters simply go nowhere.
555B
CP 30H FE 30
Compare Register A, the masked printer status, against 30H, the pattern a connected and ready Model I printer presents. If Register A equals 30H the Z FLAG is set; any other pattern means busy, out of paper or deselected and leaves the NZ FLAG set.
555D
RET C9
Return with the flags from that comparison: the Z FLAG set means the printer will take a character, the NZ FLAG set means the caller must wait. Register A still holds the masked status, which is what a control request for status receives.
555EH - Output Entry
Reached from 5520H when a byte is being printed. It establishes Register Pair IY over the seven-byte parameter block, runs the filter, and returns the "device ready, no error" condition the caller expects from a driver.
555E
PUSH IY FD E5
Save Register Pair IY onto the stack. The filter takes the register over for the duration of the call and must give the caller back what it had.
5560
Point Register Pair IY at 5517H, the first byte of the resident parameter block, at module base + 12H. Every formatting parameter is then reached as (IY+00H) through (IY+06H). The operand is relocation table entry 5 at 54EBH.
5564
PUSH AF F5
Save Register A and the flags. Register A holds the byte the ROM dispatcher was given, and the filter is about to destroy both it and the flags.
5565
GOSUB to the character filter at 556DH, which does all the work: translation, control-character routing, margin, wrap, tab expansion and page accounting. The operand is relocation table entry 6 at 54EDH.
5568
POP AF F1
Restore Register A and the flags saved at 5564H, so the caller sees the byte it supplied rather than whatever the filter left behind.
5569
CP A BF
Compare Register A against itself. The result is always equal, so this sets the Z FLAG and clears the CARRY FLAG without changing Register A. That is the condition an LDOS output driver returns to mean the device accepted the byte and reported no error.
556A
POP IY FD E1
Restore Register Pair IY with the caller's value, saved at 555EH. POP does not disturb the flags, so the ready condition just set survives.
556C
RET C9
Return to whoever called $PUT on the printer.
556DH - The Character Filter
The routing decision for one outgoing character. Translation is applied first, so a translated character is then treated as whatever it became. Carriage return and line feed both mean "new line". Form feed skips to top of form without counting a line. 06H is the top-of-page reset that the DOS manual documents as LPRINT CHR$(6). A tab is expanded only when the TAB switch is on. Anything else below 20H is passed through untouched, so printer control sequences survive the filter intact.
556D
LD A,(IY+01H) FD 7E 01
Fetch (IY+01H), the XLATE source character at 5518H in the parameter block, into Register A. It is the aa half of the X'aabb' the user gave, or zero when XLATE was not specified.
5570
CP C B9
Compare Register A, the XLATE source character, against Register C, the character on its way to the printer. If they are equal the Z FLAG is set.
5571
If the NZ FLAG has been set (this character is not the one being translated), JUMP to 5576H and leave Register C alone.
5573
LD C,(IY+02H) FD 4E 02
Load Register C with (IY+02H), the XLATE replacement character at 5519H in the parameter block, the bb half of X'aabb'. From this point on the substituted character is the one being processed, so it is subject to every rule below.
5576
LD A,C 79
Copy Register C, the character to be printed after any translation, into Register A for the sequence of comparisons that follows.
5577
CP 0DH FE 0D
Compare Register A, the outgoing character, against 0DH, ASCII carriage return. If Register A equals 0DH the Z FLAG is set.
5579
If the Z FLAG has been set (the character is a carriage return), JUMP to 55DFH to end the line and account for it against the page.
557B
CP 0AH FE 0A
Compare Register A, the outgoing character, against 0AH, ASCII line feed.
557D
If the Z FLAG has been set (the character is a line feed), JUMP to 55DFH. The filter treats a bare line feed exactly as it treats a carriage return, and the routine it jumps to decides which characters actually reach the printer.
557F
CP 0CH FE 0C
Compare Register A, the outgoing character, against 0CH, ASCII form feed.
5581
If the Z FLAG has been set (the character is a form feed), JUMP to 55ECH, which is the page-advance half of the new-line routine. Entering there skips both the carriage-return emission and the line-counter increment, so an explicit form feed spaces out the rest of the current page without being counted as a printed line.
5583
CP 06H FE 06
Compare Register A, the outgoing character, against 06H. The DOS manual documents this as the top-of-page reset: LPRINT CHR$(6) tells the filter that the paper has been repositioned by hand.
5585
If the Z FLAG has been set (the character is 06H), JUMP to 5609H, which resets the line counter in DCB+04H to the top-of-page value and returns. Nothing is sent to the printer. The operand is relocation table entry 17 at 5503H.
5588
BIT 2,(IY+04H) FD CB 04 56
Test bit 2 of (IY+04H), the flag byte at 551BH in the parameter block. Bit 2 is the TAB switch, set at 527EH when the TAB parameter was supplied. The Z FLAG is set when tab expansion is switched off.
558C
If the Z FLAG has been set (tab expansion is off), JUMP to 5592H, leaving a 09H character to be handled by the general control-character rule and passed through unchanged.
558E
CP 09H FE 09
Compare Register A, the outgoing character, against 09H, ASCII horizontal tab.
5590
If the Z FLAG has been set (the character is a tab and expansion is on), JUMP to 560EH to replace it with spaces up to the next eight-column stop.
5592
CP 20H FE 20
Compare Register A, the outgoing character, against 20H, ASCII space, the lowest printable code. If Register A is below 20H the CARRY FLAG is set.
5594
If the CARRY FLAG has been set (the character is a control code that none of the tests above claimed), JUMP to 5535H to send it straight on without counting it. This is what lets a printer's own escape and control sequences pass through the filter untouched. The routine's RET returns to the caller of the filter.
5596H - Print a Character, with Margin and Line Wrap
The printable-character path, and also the routine the indent and tab loops call so that the spaces they emit obey the same rules. It emits the left margin when the line is empty, sends the character, counts it, and forces a new line with an indent when the CHARS limit has been reached.
5596
LD A,(IY+06H) FD 7E 06
Fetch (IY+06H), the MARGIN value at 551DH in the parameter block, into Register A. It is the number of spaces that must precede the first character of every line.
5599
AND A A7
AND Register A with itself, holding the MARGIN value, purely to set the flags. The Z FLAG is set when no margin was asked for.
559A
If the NZ FLAG has been set (a margin was specified), GOSUB to 5619H with the margin width in Register A. That routine emits the spaces only when the column counter is zero, so the margin appears once per line and not before every character. The operand is relocation table entry 7 at 54EFH.
559D
GOSUB to 5535H to send the character in Register C onward to the driver behind the filter. The operand is relocation table entry 8 at 54F1H.
55A0
INC (IY+00H) FD 34 00
INCrement (IY+00H), the column counter at 5517H in the parameter block, by 1. It now holds the number of printable characters emitted on this line; margin spaces are not counted here because 5619H bypasses this routine.
55A3
LD A,(IY+05H) FD 7E 05
Fetch (IY+05H), the CHARS value at 551CH in the parameter block, into Register A. It is the number of printable characters allowed on a line.
55A6
AND A A7
AND Register A with itself, holding the CHARS value, purely to set the flags. The Z FLAG is set when CHARS is zero.
55A7
RET Z C8
If the Z FLAG has been set (CHARS is zero, so no line-length limit was set), return at once. Lines then run on for as long as the caller sends characters.
55A8
DEC A 3D
DECrement Register A, the CHARS value, by 1, giving the highest column number that may be occupied before a wrap is due.
55A9
CP (IY+00H) FD BE 00
Compare Register A, the CHARS limit less one, against (IY+00H), the column counter at 5517H. If the column counter is the larger, the CARRY FLAG is set, meaning the line is now full.
55AC
RET NC D0
If the NO CARRY FLAG has been set (the column counter has not yet passed the limit), return; there is still room on the line.
55AD
GOSUB to 55DFH to end the line: emit the carriage return and line feed, zero the column counter, advance the page line counter and, if the printed area is full, skip to top of form. The operand is relocation table entry 9 at 54F3H.
55B0
LD A,(IY+03H) FD 7E 03
Fetch (IY+03H), the INDENT value at 551AH in the parameter block, into Register A. It is the number of spaces the continuation line is to start with.
55B3
AND A A7
AND Register A with itself, holding the INDENT value, purely to set the flags.
55B4
RET Z C8
If the Z FLAG has been set (no indent was asked for), return; the wrapped line starts at the margin.
55B5
PUSH BC C5
Save Register Pair BC. Register C holds the character the caller is printing and must survive the run of spaces; Register B is about to become the count. The tab expander at 560EH jumps in here with its own count already in Register A.
55B6
LD B,A 47
Copy Register A, the number of spaces to emit, into Register B as the loop counter.
55B7
LD C,20H 0E 20
Loop Start
Load Register C with 20H, ASCII space, the character to be printed.
55B9
GOSUB back to the top of this routine at 5596H to print the space. Going the long way round means each space is counted in the column counter and is itself subject to the margin and wrap rules, so an indent wider than the line cannot run away. The operand is relocation table entry 10 at 54F5H.
55BC
DECrement Register B, the count of spaces still to emit, and LOOP BACK to 55B7H if it has not reached zero. Loop End
55BE
POP BC C1
Restore Register Pair BC, returning the caller's character to Register C.
55BF
RET C9
Return to the caller.
55C0H - Carriage Return and Line Feed
What actually goes down the wire when a line ends. With ADDLF the filter always sends carriage return then line feed. Without it, a line that has content ends with a carriage return, and a line that is already empty is given a line feed instead, which is what makes LPRINT with no arguments produce a blank line on a printer that would otherwise ignore a bare carriage return.
55C0
BIT 0,(IY+04H) FD CB 04 46
Test bit 0 of (IY+04H), the flag byte at 551BH in the parameter block. Bit 0 is the ADDLF switch, set at 527EH when the ADDLF parameter was supplied. The Z FLAG is set when ADDLF is off.
55C4
If the Z FLAG has been set (ADDLF is off), JUMP to 55CDH, where the character sent depends on whether the line has any content.
55C6
LD C,0DH 0E 0D
Load Register C with 0DH, ASCII carriage return, the first of the two characters ADDLF sends.
55C8
GOSUB to 5535H to send the carriage return in Register C onward to the driver behind the filter. The operand is relocation table entry 11 at 54F7H.
55CB
JUMP to 55D5H to load a line feed and send that too, which is the whole of what ADDLF means.
55CD
LD A,(IY+00H) FD 7E 00
Fetch (IY+00H), the column counter at 5517H in the parameter block, into Register A. It is zero when nothing printable has been put on this line.
55D0
AND A A7
AND Register A with itself, holding the column counter, purely to set the flags. The Z FLAG is set on an empty line.
55D1
LD C,0DH 0E 0D
Load Register C with 0DH, ASCII carriage return, the character an occupied line ends with. Loading a register does not disturb the flags just set.
55D3
If the NZ FLAG has been set (the line has content), JUMP to 55D7H and send that carriage return.
55D5
LD C,0AH 0E 0A
Load Register C with 0AH, ASCII line feed. Reached either from 55CBH, as the second half of the ADDLF pair, or by falling through from 55D3H when the line is empty and a carriage return alone would leave the paper where it is.
55D7
GOSUB to 5535H to send the character now in Register C, a carriage return or a line feed, onward to the driver behind the filter. The operand is relocation table entry 12 at 54F9H.
55DA
LD (IY+00H),00H FD 36 00 00
Store zero into (IY+00H), the column counter at 5517H in the parameter block. The line is finished, so the next printable character starts a new one and will trigger the left margin again.
55DE
RET C9
Return to the caller, which is either the new-line routine at 55DFH or the top-of-form loop at 5603H.
55DFH - New Line, Line Count and Page Advance
The page accounting. The three counters it works on live in the printer Device Control Block, reached through Register Pair IX, which the ROM device dispatcher set before the filter was entered: DCB+03H is the physical page length, DCB+04H the current line number and DCB+05H the number of printed lines allowed. Both DCB+03H and DCB+05H carry the plus-one bias the installer applied at 531AH and 5325H unless SLINE selected the Model I convention, which removed it again at 5336H.
55DF
GOSUB to 55C0H to end the physical line, sending the carriage return and line feed the ADDLF setting calls for and zeroing the column counter. The operand is relocation table entry 13 at 54FBH.
55E2
INC (IX+04H) DD 34 04
INCrement DCB+04H, the current line number within the page, by 1. Register Pair IX addresses the printer Device Control Block throughout the resident code because the ROM device dispatcher at 03C2H set it before the driver was entered.
55E5
LD A,(IX+04H) DD 7E 04
Fetch DCB+04H, the line number just incremented, into Register A.
55E8
CP (IX+05H) DD BE 05
Compare Register A, the current line number, against DCB+05H, the number of printed lines allowed on a page as written by the installer at 532CH. If the line number is the smaller the CARRY FLAG is set.
55EB
RET C D8
If the CARRY FLAG has been set (the printed area of the page is not yet full), return. This is the ordinary end of a line.
55EC
LD A,(IX+03H) DD 7E 03
Fetch DCB+03H, the physical page length as written by the installer at 531BH, into Register A. Entered directly from 5581H when the caller sent an explicit form feed, in which case neither the line was ended nor the line counter advanced.
55EF
SUB (IX+04H) DD 96 04
SUBtract DCB+04H, the current line number, from Register A, the physical page length. Register A now holds the number of lines between here and the foot of the physical page. The Z FLAG is set when there are none.
55F2
If the Z FLAG has been set (the paper is already at the foot of the page), JUMP to 5609H to reset the line counter without moving the paper at all.
55F4
PUSH BC C5
Save Register Pair BC. Register C still holds the character that started all this and Register B is about to be used as a line count.
55F5
LD B,A 47
Copy Register A, the number of lines remaining to the foot of the page, into Register B as the loop counter.
55F6
BIT 1,(IY+04H) FD CB 04 4E
Test bit 1 of (IY+04H), the flag byte at 551BH in the parameter block. Bit 1 is the FFHARD switch, set at 527EH when the FFHARD parameter was supplied. The Z FLAG is set when it is off.
55FA
If the Z FLAG has been set (FFHARD is off), JUMP to 5603H to space down to the top of the next page with a run of line feeds instead of a form-feed character.
55FC
LD C,0CH 0E 0C
Load Register C with 0CH, ASCII form feed, the single character FFHARD sends in place of the whole run of blank lines. The DOS manual warns that the printer must recognise it.
55FE
GOSUB to 5535H to send the form-feed character in Register C onward to the driver behind the filter. The operand is relocation table entry 14 at 54FDH.
5601
JUMP to 5608H, skipping the line-feed loop entirely; the printer's own form-feed mechanism has moved the paper.
5603
Loop Start
GOSUB to 55C0H to emit one blank line. The column counter is zero on every pass, so with ADDLF off the routine sends a line feed and with ADDLF on it sends a carriage return and a line feed. The operand is relocation table entry 15 at 54FFH.
5606
DECrement Register B, the number of lines still to be skipped to reach the foot of the page, and LOOP BACK to 5603H if it has not reached zero. Loop End
5608
POP BC C1
Restore Register Pair BC, returning the caller's character to Register C.
5609
LD (IX+04H),01H DD 36 04 01
Self-Modifying Code
Store the top-of-page line number into DCB+04H, the current line number in the printer Device Control Block. The immediate operand at 560CH is not a fixed constant: the installer overwrote it at 526AH with the SLINE value, so it is 01H under the Model III convention and 00H under the Model I convention. This is also the instruction a 06H character reaches directly from 5585H, which is how LPRINT CHR$(6) tells the filter that the paper has been repositioned by hand.
560D
RET C9
Return to the caller.
560EH - Tab Expansion
Reached from 5590H when a 09H arrives and the TAB switch is on. Tab stops are fixed at every eighth column, and the count is worked out from the column counter rather than from any stored tab table.
560E
LD A,(IY+00H) FD 7E 00
Fetch (IY+00H), the column counter at 5517H in the parameter block, into Register A. It is the number of printable characters already on this line.
5611
AND 07H E6 07
AND 07H with Register A, the column counter, leaving the position within the current eight-column group, a value from 0 to 7.
5613
SUB 08H D6 08
SUBtract 08H from Register A, the position within the group. The result is negative, from minus 8 to minus 1.
5615
NEG ED 44
Negate Register A, turning that into a positive count from 1 to 8: the number of spaces needed to reach the next tab stop. A column counter that is already on a stop yields a full eight-space advance.
5617
JUMP to 55B5H, the space-emitting loop that the indent handling also uses, with the count already in Register A. Each space goes through 5596H, so the expanded tab obeys the margin and the line-wrap limit like any other printable character, and the routine's RET returns to the caller of the filter.
5619H - The Left Margin
Called from 5596H before every printable character, with the MARGIN width already in Register A, but only actually emitting anything when the column counter is zero. The spaces are sent through 5535H rather than through 5596H, so they are not counted in the column and do not consume any of the CHARS line width.
5619
PUSH BC C5
Save Register Pair BC. Register C holds the character the caller is about to print and must survive the margin, and Register B is about to become the loop counter.
561A
LD B,A 47
Copy Register A, the MARGIN width fetched by the caller from (IY+06H), into Register B as the count of spaces.
561B
LD A,(IY+00H) FD 7E 00
Fetch (IY+00H), the column counter at 5517H in the parameter block, into Register A. A margin belongs only at the very start of a line.
561E
AND A A7
AND Register A with itself, holding the column counter, purely to set the flags. The Z FLAG is set only on an empty line.
561F
If the NZ FLAG has been set (the line already has characters on it), JUMP to 5628H and emit nothing; the margin has already been laid down for this line.
5621
LD C,20H 0E 20
Loop Start
Load Register C with 20H, ASCII space.
5623
GOSUB to 5535H to send the space in Register C straight to the driver behind the filter, bypassing 5596H so that the column counter is left alone and the margin does not eat into the CHARS width. The operand is relocation table entry 16 at 5501H.
5626
DECrement Register B, the number of margin spaces still to emit, and LOOP BACK to 5621H if it has not reached zero. Loop End
5628
POP BC C1
Restore Register Pair BC, returning the caller's character to Register C.
5629
RET C9
Return to 559DH to print the character itself. This is the last byte of the resident module; 5505H through 5629H inclusive is the 0125H bytes the installer relocates.