The Computer Journal, Issue 35

Z-System Corner

© Jay Sage

Reproduced with permission of author and publisher.

My column about shells and WordStar Release 4 (WS4) in TCJ issue #33 prompted more than the usual level of commentary. There were extensive discussions on Z-Node-Central and the Lillipute Z-Node (the official TCJ bulletin board), and several messages reached me over the ARPA network. Not all of the comments were favorable, but I was nevertheless happy to receive them. They helped further clarify my thinking on the very important subject of shells and have spurred me on to prove my points by actually converting WS4 to a ZCPR2-style shell! After a bit of follow-up discussion, I will describe how this conversion was accomplished.


There were some things I said in the previous column that were factually wrong, and before I do anything else I wish to correct them.

First, I stated that the Z-System code in WS4 was written by someone other than MicroPro. I was wrong. David McCord, who was vice president at Echelon at the time WS4 was developed, sent me a message with the facts of this matter. Echelon, through staff like David and through published materials, educated Peter Mireau of MicroPro on the facilities and philosophy of Z-System. Peter did all the actual programming, so the coding mistakes were his fault, not Echelon's or David's.

From a broader perspective, however, as I stated in the previous column, the real culprit was inadequate testing. Bugs in the code would have been discovered and conceptual issues clarified had more people in the Z community been involved as beta testers. There are so many different styles of using Z-System that it takes a number of testers to uncover problems. Within days after copies of WS4 were delivered to users of my Z-Node, I started getting questions about strange behavior exhibited by WS4, behavior that turned out to result from its operation as a shell.

A second mistake in the earlier column was my implication that WS4 does not get its own name from the ZCPR3 external file control block (XFCB). I no longer remember what made me think that this was the case, but David McCord assured me (and I have now verified for myself) that WS4 does, indeed, get its name from the XFCB when it sets up the shell stack entry.

Finally, one reader reported to me that my WSSHLOFF routine (the one that completely disables shells while WS4 is running and reenables them when WS4 terminated) crashed his system. Unfortunately, a large number of misprints crept into the listings in going from my disk file to the printed pages. Most of the typos were obvious, but one was compounded by a double error. In the WSSHLOFF listing, the value for EXITSUB was printed as 03BVh. The 'V' was obviously a mistake, and clever readers looked at the similar listing for WSSHLFIX, where the value was given as 03B3h. This, regrettably, looks correct but was also a typo. The proper value is 03BEh. We hope that Art Carlson will make sure that the listings in this column are transcribed accurately (so that all the mistakes will be mine!).

More WS4 Comments

While on the subject of WS4, I would like to add a few further comments about how it works. Not surprisingly (considering when it was developed), in creating its shell stack entry WS4 does not make use of the facility introduced with ZCPR version 3.3 that allows a program to determine from the XFCB not only its name but also the directory from which it was actually loaded (the user number is at offset 13 and the drive, with A=1, at offset 14).

As a result, in order for WS4 to be reinvoked as a shell, the command search path must include the directory in which WS4 is located. I mention this here as a reminder and suggestion to authors of new or updated shells and error handlers that they use this Z33 facility to avoid the requirement that the program be on the path and to speed up loading of the program (by eliminating any search for it). My WordStar conversion described later adds this feature.

With WS4 it is generally necessary that the command search path include WS4's directory for an additional reason. I learned the hard way that when WS4 runs under Z-System, it pays no attention to the drive and user number that WSCHANGE specified as the location for the overlay files; it only uses the search path to try to locate them.

This is a problem for me because, as I have explained at length in previous columns, I put only my small RAM disk on the path and use ARUNZ aliases to invoke all programs except the very few that fit on the RAM disk. With this approach, there is no way to get WS4 to find its overlay files. The conversion addresses this problem also.

ZCPR2 vs. ZCPR3 Shells

I would not like to take up again one of the subjects raised in issue #33: ZCPR2-style versus ZCPR3-style shells.

First an aside. Shells seem to be a surprisingly emotional issue. I thought my earlier column presented a fairly carefully and calmly reasoned discussion of some aspects of shells, including their pros and their cons. Some readers, however, took great offense at my even questioning the current method of implementing shells or of what some people are trying to do with them.

One reader went so far as to suggest that I had no business commenting on the subject when, by my own admission, there are a number of shells that I have never used. Besides the fact that this is hardly a reasoned argument, I would like to make sure that the following facts about shells are fully appreciated.

ZCPR3-style shells are a facility of the command processor. Without special code in the CPR, there would be no such shells. As the author of the two latest versions of the ZCPR command processor, I think I can speak with some authority (though certainly not with infallibility) on the subject, since in writing that code I had to consider the issue of shells rather carefully from a rigorous theoretical viewpoint.

ZCPR2-style shells, quite the contrary, are not a facility of the command processor; they are a facility of the individual shell programs. Their functioning depends only on the operation of the multiple command line facility. The command processor does not treat a Z2 shell command any differently than it treats any other command. This is really the key to the difference between the two shell implementations.

In the previous column I stated: "...I am coming to the conclusion that only programs like history shells...should be implemented as ZCPR3-style shells. Other programs, like ZFILER and WordStar should use the ZCPR2 style." I then invited readers to enlighten me if I was missing some important point. I got some responses to this invitation, but no one yet has offered me any evidence that I had missed any important point.

One reader reiterated essentially the same difference between Z2 and Z3 shells that I attempted to demonstrate with my example in which WordStar was invoked in a multiple command line. Apparently the point bears repeating.

This reader presented the point using a command line like the following:


Under ZCPR2, ZFILER would run and present its file display to the user. If the user generated a command line "CMDLINE" as the result of a macro or in response to the prompt after the 'Z' command, a Z2-shell version of ZFILER would build the command sequence "CMDLINE;ZFILER" and insert it into the multiple command line buffer just before the next command to be executed. This would give:


The user's command line would run, and then ZFILER would be invoked again. Only on termination of ZFILER would the last command, "ECHO TESTING", be performed.

A Z3 shell would respond to the same command line from the user in quite a different way. As before, ZFILER would be invoked first. It would determine from the Z3 message buffer that it had been invoked manually and would respond by pushing its own name onto the shell stack. Then it would terminate. The command processor would then proceed to run "ECHO TESTING". Only after that, once the command line was empty, would ZFILER be reloaded, this time as a shell. Recognizing its shell status, it would now display its screen of file names and do its real work.

The reader who submitted this example, if I understood him correctly, viewed the Z3 behavior as correct and the Z2 behavior as wrong. If you are an experienced Z-System user, you will probably recognize in this reader a fellow expert (and, indeed, he is). He is so used to ZCPR3 that he no longer notices that it is the behavior of the Z3 shell that is truly bizarre!

Consider the following two command lines:


We have already analyzed the first one; the second one can safely be left as an exercise for the reader. We will simply state the answer that under ZCPR3 they will accomplish exactly the same thing! This is hardly a result that conforms to intuition, and I still remember in my early days as a Z-Node sysop trying to explain to quite a few users why the second command on a VFILER command line executes first!

Under ZCPR2 the result is just what one would expect. In the first case, ZFILER runs first, and ECHO runs only after the user terminates ZFILER using its 'X' command. In the second case, ECHO runs first and ZFILER second. In other words, with Z2 shells, commands are executed in the order they are entered, a notion that does not require long experience and great expertise to understand and get used to! And it gives the user a greater measure of control.

Mixed Z2 and Z3 Shells

The same reader submitted another interesting example that illustrates the confusing behavior that can arise when Z2 and Z3 shells are mixed. Here we assume that WordStar has been implemented as a Z2 shell and ZFILER as a Z3 shell. Suppose we use the 'R' command of WordStar to enter the command "ZFILER". WS4, as a Z2 shell, would generate the command line


ZFILER, as a Z3 shell, would install itself on the shell stack and proceed to execute "WS". ZFILER would not run in its file maintenance mode until after we terminated WordStar.

This is, admittedly, probably not what one intended, since we most likely entered the ZFILER command with the intention of doing some file maintenance before returning to WordStar. On the other hand, it is certainly no more bizarre than what we saw in our earlier example.

If both WS4 and ZFILER were Z3 shells, then the invocation of ZFILER from the WS4 'R' command would cause it to become the active shell (the one on the top of the shell stack). The WS4 shell would be pushed down in the shell stack, and ZFILER would take control. With a little thought, however, you will see that the same is also true if both ZFILER and WS4 are Z2 shells!

The strange behavior with the mixed shells in the above example arises in part because ZFILER was not really being used as a shell in the Z3 sense, namely, as a replacement for the command processor's command-line input routine. It was intended as a file maintenance utility.

Suppose we had entered the command "EASE" (the Z3-type history shell) instead of "ZFILER" from our Z2 version of WordStar. This would establish EASE as the current shell and return to WordStar. That behavior would not seem strange, because in this case we would be thinking of our EASE command as establishing the shell to be used in place of the command processor the next time the command processor needed a new command line. So long as WordStar is running, there is no need for EASE to do anything. We expect it to take effect only after we are finished using WordStar.

Nested Z2 Shells and Recursive Aliases

Although I had once thought that the Z3 shell stack was required in order to nest shells, I showed in the earlier column that this is not the case. Z2-style shells can, in fact, be nested more flexibly. There is no predetermined limit to the nesting depth or to the amount of information that can be passed with each shell command line. The only limit is imposed by the length of the multiple command line buffer, just as with the nesting of aliases.

With the standard shell stack configuration of 4 32-byte entries, if a shell command uses only 16 bytes, 16 bytes are wasted. On the other hand, if a shell command needs 48 bytes to hold its information, it cannot run at all under this configuration (NZ-COM can come to the rescue by allowing the shell stack configuration to be changed on the fly). With Z2 shells, these problems go away. In 64 bytes of command line, one can have two 32-byte shell commands or a combination of one 16-byte shell command and one 48-byte shell command (or five 12-byte shell commands).

I did overlook one point when I described putting data for the shells on the command line. In the Z3 shell stack, one can include, after the shell command's terminating null, any binary data that one wishes. Thus 256 values are possible for each extra byte in the shell stack entry.

In order to carry shell data on the command line, several additional constraints apply. First, the command processor strips the high bits off all characters in the command line, so only 128 values are available to start with. Secondly, the null character cannot be used because the command processor would interpret this as the end of the command line (that leaves 127 values). Finally, letters are converted to upper case, thereby making the characters from 'a' to 'z' inaccessible (scratch another 26). This leaves only 101 possible values out of the original 256. Moreover, extra characters are required as flags to signal the program to consider itself as having been invoked as a shell (a service provided in ZCPR3 by a flag in the message buffer). All of these things reduce the efficiency with which the space in the command line buffer can be used compared to the space in the shell stack.

One reader pointed out that recursive aliases cannot be used with Z2-type shells. This is true... but only if one is using the pseudo-recursive alias that I invented. This kind of alias accomplishes a crude approximation to recursion by discarding any pending commands in the command line buffer. This will, indeed, discard any shell reinvocation commands. However, if one uses the logically sound and rigorous recursive alias technique invented by Dreas Nielsen (see my column in issue #28), there is no problem. It sometimes pays to do things right!

In fact, it seems to me that the Z2 shell is, in essence, a recursive alias, a program that keeps invoking itself. And this is just what most (if not all) Z3 shells actually do. I am still awaiting an example of something (good) that a Z3 shell can do that cannot be done in some equivalent way with a Z2 shell or recursive alias.

The Real Difference Between Z2 and Z3 Shells

After much reflection, I think I have finally put my finger on the fundamental distinction between Z2 and Z3 shells. It derives from the facts I alluded to earlier: that the Z3 shell is a true creature of the command processor and the Z2 shell is not.

Here is an example that will illustrate the point. Suppose the history shell EASE were implemented as a Z2-style shell and that while it is running, we issue the command "DIR". EASE will insert into the command line a sequence like the following:


DIR will run, and then EASE will be reinvoked. Looks fine! But now suppose the user enters the command "IF EXIST FN.FT". EASE will then generate the command line


If the file FN.FT exists, this will again work just fine, but suppose the file does not exist. Then the system will enter a false flow state, and the EASE command (and perhaps other commands pending in the command line after it) will be flushed by the command processor. The shell function will be lost, and any other pending commands will be processed in an unintended way.

For a Z2 shell to function properly in general, all command lines inserted by it must result in the same flow state at the end of the command line as at the beginning. With a MENU shell it could be possible for the system designer to guarantee this, since he can control which commands are generated by the shell. With a history type shell it would be nearly impossible to ensure that this condition would always be met.

The critical feature of shell processing under ZCPR3 is that _flow processing is suspended during the operation of shells_. This allows them to run, as they must, even after the user has passed a command that leaves the system in a false flow state. The ZCPR33 Users Guide goes into some detail on this matter, and had I remembered better what I wrote there, it would not have taken me this long to come to the essence of the Z2-vs.-Z3 shell issue.

Some users of ZCPR33 have modified the way the command processor deals with flow control in shell processing. No one has yet convinced me of the value of this (the risks are undeniable). It still seems to me that Z2-type shells and recursive aliases can accomplish the same thing, but in a logically sound way.

I have extended an invitation to Dreas Nielsen to write a series of columns for TCJ explaining his very powerful shell programs. Since he is also one of the people who has made this modification to the CPR, perhaps he will also present the other side of this story and explain why it is necessary or desirable to treat shells the way he does.

Remaking WordStar Release 4

When I first received my copy of WordStar 4 and encountered problems with the way it handled shells, I fired up the DSD debugger and tried to figure out how to fix it. After a considerable amount of rummaging about in the code (and especially trying to figure out what was going on inside WS.OVR), I gave up. Later I tried again... and failed again. In the course of preparing this column, I decided to have one more go at it, and this time things started to click.

The patches I will describe here are preliminary and have not yet been extensively tested. In fact, as I write this, I am the only one who has used them, and you know what I said above about the dangers of a test program that does not involve a variety of Z-System users. So, you are hereby recruited, if you are willing, to join the test program.

Since I may very well have made some mistakes, and since there are further changes that people may want to make (let's hear your suggestions), I will not only give the results; I will describe the process by which these patches have been developed.

Cracking the Code

The first step toward changing the code was figuring out how the virgin WordStar was doing what it did. In particular, I wanted to locate routines related to Z-System functions, so the first thing I tried was searching for all references to address 109h, which contains the address of the Z-System environment (ENV). Any WS4 feature that made use of a Z-System facility would have to get information from the ENV.

As best I recall, this did not turn up many references and did not particularly help (though it was a good idea, and that's why I mention it). In the end, I just started tracing the code from the beginning, figuring that WS4 would have to determine fairly early whether it was running under Z-System or standard CP/M. This turned out to be correct, and very soon I came to the key Z routine, at address 0AA4h in WS.COM. This routine returns the address and size of a Z-System module specified by an offset passed in the E register.

Having discovered this routine, I used DSD to find all references to it in WS.COM and WS.OVR. They occur with the following values of E:

E = 9h the command search path (PATH)
E = 15h the named directory register (NDR)
E = 18h the multiple command line buffer (MCL)
E = 1Eh the shell stack (SHL)
E = 22h the message buffer (MSG)
E = 24h the external file control block (XFCB)

Setting up the Shell Stack

The block of code beginning around address 3CBFh in WS.OVR makes references to MCL, XFCB, and SHL. I guessed correctly that this had to be the code where WS4 sets up its shell stack entry. (This block of code, by the way, is where the shell-pushing mistake occurs for the case where the shell stack is currently empty.)

The patch for this part of WS.OVR (see Listing 1) modifies this code. First of all, since WS4 is going to operate as a Z2-type shell, we do not want it to do anything with the shell stack. It is easy to disable the code by simply skipping over it, but one has to watch out for subtleties. Indeed, in order for the 'R' command to use the MCL and not chain using the greatly inferior CP/M method, WS4 has to think that the shell entry was established successfully.

I noticed that a flag was being set into address 2200h, and I surmised that it is used by WS4 to show that it is running under Z-System. In the patch, I set this flag even though the shell stack entry is not being set up. I have not examined all references to this flag, and there is a chance that there are additional, more complex effects. If any problems appear with the patched version of WordStar, this flag might be involved. For the initial attempt at fixing WS4, I just took the easiest course of action, and so far it appears to have worked.

It seemed foolish to waste space in WS.OVR by doing nothing more than setting the flag and jumping to where the original code resumed (60AAh). Instead, I have used the space to compute the command line necessary to reinvoke WordStar. The code gets not only the name by which WordStar was invoked but also the drive and user number from which it was loaded. A command line of the form ";DUU:WSNAME" is generated.

There is one extra step in this part of the patch. When running as a Z3 shell, WS4 knows from the command status flag in the message buffer when it was invoked as a shell so it can put up the press-any-key message before clearing the screen and resuming operation. As a Z2 shell, WS4 cannot use this facility. Instead, a signal has to be included in the command tail. For reasons that I will not go into in full detail, I chose for this signal a comma at the very end of the tail. Very briefly, the comma is a handy character because it is not parsed into the default file control blocks, where a program could confuse it with a file name.

The final reinvocation command line, with its terminating null, takes the form


Since I could not be sure that this section of overlay code would persist in memory until the command would be used, I store it at the top of the WS4's user patch area (MORPAT).

The Initialization and Termination Patches

Having made the above modification, we must make two others in order to remain consistent. First, we must modify the WS4 initialization code in which it determines whether or not it was invoked as a shell. This is the patch at address 1A2Fh in WS.COM. The patch calculates the address of the last character passed in the command tail and checks to see if it was a comma. If not, it proceeds with normal operation of the program.

If there is a comma, the shell-wait message must be displayed until the user presses a key. But one must also remove the comma from the command tail to ensure that WordStar not think it has been passed a file name. At present I do this by replacing the comma with a space. This is not rigorous, but it seems to work, since WS4 is apparently not confused by a tail consisting only of spaces (unfortunately, a number of programs are).

Since WS4 no longer pushes its name onto the shell stack, we must also prevent it from popping the shell stack when it terminates. This is the patch at address 13CEh in WS.COM. This is the easiest patch of all, since we simply have to skip some code. As an additional benefit, this frees up about 40 bytes of space that we use for some of our other patch code.

Fixing the 'R' Command

Now we come to the main item in this set of patches - the code that makes the 'R' command work as a ZCPR2-type shell. The new code here is much more complex than what it replaces, and we can only fit part of it at the original location 67B2h in WS.OVR. We put what we can there and continue with the rest in the MORPAT area in WS.COM.

The basic strategy is to take the command line entered by the user in response to the 'R' command prompt, append the WS reinvocation command (including its semicolon separator), and append any remaining command line pending in the multiple command line buffer (if there is one, it will begin with a semicolon also). If there is enough room for the result in the MCL, then it is moved there and chained to. If not, a warning message is displayed on the screen until a key is pressed, and the user command is ignored.

To implement this strategy, I chose the simplest method I could think of. Since the 'R' command operates from the WordStar no-file menu, the entire WS edit buffer is available as a scratch area. I picked an arbitrary address of A000h for a buffer in which to build the new command line. Again, rigorous code calculate the address. My code is a quick-and-dirty solution.

Finding the Overlay Files

As I noted earlier, with my style of operation, WS4 had trouble finding its overlays. To solve that problem, the patch includes an optional section to install an internal search path for the overlay files. This patch is installed at address 0F5Fh in WS.COM, where it replaces a call for the location of the Z-System path with a call to a routine that returns the address and size of an internal path. In Listing 1 the internal path has the single element B4:, the directory in which I keep my WordStar program files. You can put any values you want here.

Installing the Patches

It is not possible to install the patches in WS.OVR using MLOAD or a debugger, because the OVR file is too large to load entirely into memory. ZPATCH, on the other hand, can handle the job splendidly. ZPATCH assumes an offset of 0000 for a file of type OVR, while the addresses in the listing are those shown when the file (as much as can fit) is read into memory under a debugger. To make things consistent, you should use the ZPATCH 'O' command to set the offset to 100.

Key in the new data carefully, checking from time to time that the address is still correct. Also, be careful not to go beyond a record boundary while in ZPATCH. It wraps from the end of the record back to the beginning of that record without warning (this really gave me grief until I caught on to the problem). When you get to the end of the current record, write it out (^W), advance to the next record (>), and reenter edit mode (E). Then you can resume entering data.

The attached listing was made with a specially configured version of the SLR Z80ASM assembler. Normally, I have it display addresses in logical order for easier interpretation. For hand keying of a patch, however, it is far more convenient to have the bytes of a word in physical order. Just watch out when reading the displayed symbol values. They, too, are stored in byte-reversed format.

It is possible to use MLOAD to install just the patches for WS.COM. Simply delete the parts of WSPAT.Z80 that refer to patches in WS.OVR and assemble the remaining code to a HEX file.

Enjoy playing with (and using) this different (improved) version of WordStar 4, and let me know what you think and what further suggestions you have.



; Program:      WordStar Shell Modification Patches
; Author:       Jay Sage
; Date:         August 7, 1988
3CBF  1E 24               ld      e,24h
3CC1  CD A40A             call    envoff
3CC4  E5                  push    hl
3CC5  11 0D00             ld      de,0dh
3CC8  19                  add     hl,de
3CC9  46                  ld      b,(hl)
3CCA  23                  inc     hl
3CCB  7E                  ld      a,(hl)
3CCC  C6 40               add     a,'A'-1
3CCE  21 CB04             ld      hl,namebuf
3CD1  36 3B               ld      (hl),'
3CD3  23                  inc     hl
3CD4  77                  ld      (hl),a
3CD5  23                  inc     hl
3CD6  78                  ld      a,b
3CD7  0E 2F               ld      c,'0'-1
3CD9              tens:
3CD9  0C                  inc     c
3CDA  D6 0A               sub     10
3CDC  30 FB               jr      nc,tens
3CDE  C6 3A               add     10+'0'
3CE0  71                  ld      (hl),c
3CE1  23                  inc     hl
3CE2  77                  ld      (hl),a
3CE3  23                  inc     hl
3CE4  36 3A               ld      (hl),':'
3CE6  23                  inc     hl
3CE7  D1                  pop     de
3CE8  06 08               ld      b,8
3CEA              copyname:
3CEA  13                  inc     de
3CEB  1A                  ld      a,(de)
3CEC  FE 20               cp      ' '
3CEE  28 05               jr      z,copydone
3CF0  77                  ld      (hl),a
3CF1  23                  inc     hl
3CF2  05                  dec     b
3CF3  20 F5               jr      nz,copyname
3CF5              copydone:
3CF5  36 20               ld      (hl),' '
3CF7  23                  inc     hl
3CF8  36 2C               ld      (hl),','
3CFA  23                  inc     hl
3CFB  36 00               ld      (hl),0
3CFD  3E FF               ld      a,0ffh
3CFF  32 0022             ld      (zflag),a
3D02  C3 AA60             jp      60aah
67B2  21 381F             ld      hl,rcmdbuf
67B5  4E                  ld      c,(hl)
67B6  06 00               ld      b,0
67B8  23                  inc     hl
67B9  11 00A0             ld      de,scratch
67BC  ED B0               ldir
67BE  21 CB04             ld      hl,namebuf
67C1  CD D113             call    cpy2nul
67C4  C3 5B04             jp      morpat
045B  D5                  push    de
045C  1E 18               ld      e,18h
045E  CD A40A             call    envoff
0461  5E                  ld      e,(hl)
0462  23                  inc     hl
0463  56                  ld      d,(hl)
0464  EB                  ex      de,hl
0465  D1                  pop     de
0466  CD D113             call    cpy2nul
0469  21 8603             ld      hl,clrscr
046C  CD C717             call    scrnfn
046F  1E 18               ld      e,18h
0471  CD A40A             call    envoff
0474  11 00A0             ld      de,scratch
0477  47                  ld      b,a
0478              lenloop:
0478  1A                  ld      a,(de)
0479  B7                  or      a
047A  28 11               jr      z,oklength
047C  13                  inc     de
047D  10 F9               djnz    lenloop
047F  11 9F04             ld      de,errmsg
0482  0E 09               ld      c,9
0484  CD 0500             call    0005h
0487  CD 491A             call    sak
048A  C3 4E7F             jp      7f4eh
048D              oklength:
048D  11 0400             ld      de,4
0490  EB                  ex      de,hl
0491  19                  add     hl,de
0492  EB                  ex      de,hl
0493  73                  ld      (hl),e
0494  23                  inc     hl
0495  72                  ld      (hl),d
0496  21 00A0             ld      hl,scratch
0499  CD D113             call    cpy2nul
049C  C3 F613             jp      13f6h
049F              errmsg:
049F  07 4D 43 4C         db      bell,'MCL Ovfl - press any key...$'
04A3  20 4F 76 66
04A7  6C 20 2D 20
04AB  70 72 65 73
04AF  73 20 61 6E
04B3  79 20 6B 65
04B7  79 2E 2E 2E
04BB  24          
0F5F  CD D913             call    setpath
0F62  00                  nop
0F63  00                  nop
13CE  C3 F613             jp      13f6h
13D1              cpy2nul:
13D1  7E                  ld      a,(hl)
13D2  12                  ld      (de),a
13D3  B7                  or      a
13D4  C8                  ret     z
13D5  23                  inc     hl
13D6  13                  inc     de
13D7  18 F8               jr      cpy2nul
13D9              setpath:
13D9  21 E013             ld      hl,path0
13DC  7E                  ld      a,(hl)
13DD  23                  inc     hl
13DE  B7                  or      a
13DF  C9                  ret
13E0  02          path0:  db      2
13E1  02                  db      2
13E2  04                  db      4
13E3  00 00               db      0,0
13E5  00                  db      0
1A2F  21 8000             ld      hl,80h
1A32  6E                  ld      l,(hl)
1A33  CB FD               set     7,l
1A35  7E                  ld      a,(hl)
1A36  FE 2C               cp      ','
1A38  20 25               jr      nz,1a5fh
1A3A  36 20               ld      (hl),' '
1A3C  11 101B             ld      de,1b10h
1A3F  0E 09               ld      c,9
1A41  CD 0500             call    0005
1A44  CD 491A             call    sak
1A47  18 16               jr      1a5fh
1A49              sak:
1A49  1E FF               ld      e,0ffh
1A4B  0E 06               ld      c,6
1A4D  CD 0500             call    0005
1A50  B7                  or      a
1A51  28 F6               jr      z,sak
1A53  1E 0D               ld      e,0dh
1A55  0E 06               ld      c,6
1A57  C3 0500             jp      0005





; Program:      WordStar Shell Modification Patches
; Author:       Jay Sage
; Date:         August 7, 1988

; Patches to make the WordStar Release 4 'R' command operate as a ZCPR2-type
; shell.  Several routines in WS.COM and WS.OVR must be changed.
; 1. WordStar must be prevented from pushing its name onto the Z-System shell
;    stack.  However, a flag that is used by the 'R' command to determine how
;    to operate must be set as if WS4 had set itself up as a shell.
; 2. The popping of the shell stack when WS4 terminates must be disabled.
; 3. The user input to the prompt from the 'R' command must be handled
;    differently.  A command to reinvoke WS4 must be appended to the user's
;    input, and then any commands pending in the multiple command line buffer
;    must be added as well.  The result is then placed into the command line
;    buffer.  If overflow occurs, the user command is ignored, and an error
;    message is displayed until a key is pressed.  The chaining command is of
;    the form ";DUU:WSNAME ,".  The comma at the end of the command tail is
;    used as a signal that WS4 was invoked as a ZCPR2 shell.
; 4. An optional patch can be included to defeat the use of the path for
;    searching for the overlay files.  An internal path can be specified.


no      equ     0
yes     equ     not no

intpath equ     yes             ; Use internal path to find OVR files?

morpat  equ     045bh           ; Patch area
namebuf equ     morpat+128-16   ; Keep program ";DUU:PROGNAME ,<0>"
                                ; end of patch area
envoff  equ     0aa4h           ; WordStar ENV offset routine
zflag   equ     2200h           ; Z-System running flag
rcmdbuf equ     1f38h           ; Buffer for 'R' command input
rcmd    equ     rcmdbuf+1       ; Beginning of user's command line
clrscr  equ     0386h           ; Clear screen character sequence
scrnfn  equ     17c7h           ; Routine to perform screen functions
conout  equ     0280h           ; Routine to output character in A to console

bell    equ     07              ; Bell character

;                       PATCHES TO WS.OVR

; Modifications to the code that pushes WordStar onto the shell stack.

; This patch prevents the WordStar shell entry from being set up, but it
; sets the flag in 2200h that makes WordStar think that it has set it up.
; In this way, the 'R' command will work as it would with shells engaged.
; The space is used to determine the command line needed to reinvoke
; WordStar.  The ZCPR33 facility for returning the directory in which the
; program was located is used to provide an explicit DU: prefix.  The
; resulting command line is kept at the end of the user patch area.

offset  defl    2380h           ; Real address = address in overlay + offset

        org     3cbfh           ; Place to install patch

        ld      e,24h           ; Get pointer to XFCB
        call    envoff          ; HL -> XFCB

        push    hl
        ld      de,0dh          ; Offset to user number where WS.COM found
        add     hl,de
        ld      b,(hl)          ; User number to B
        inc     hl
        ld      a,(hl)          ; Drive to A

        add     a,'A'-1         ; Convert drive to letter
        ld      hl,namebuf      ; Point to buffer at end of patch area
        ld      (hl),';'        ; Command separator
        inc     hl
        ld      (hl),a          ; Store drive letter
        inc     hl

        ld      a,b             ; Now work on user number
        ld      c,'0'-1         ; Tens value
        inc     c
        sub     10
        jr      nc,tens
        add     10+'0'          ; Convert units to ASCII
        ld      (hl),c          ; Stash tens digit
        inc     hl
        ld      (hl),a          ; Stash units digit
        inc     hl

        ld      (hl),':'        ; Insert colon
        inc     hl

        pop     de              ; Get pointer to XFCB again
        ld      b,8             ; Maximum of 8 letters
        inc     de              ; Advance to next letter
        ld      a,(de)
        cp      ' '
        jr      z,copydone      ; Quit at first space
        ld      (hl),a
        inc     hl
        dec     b
        jr      nz,copyname     ; Quit after eight characters
        ld      (hl),' '        ; Put shell signal tail (a comma)
        inc     hl
        ld      (hl),','
        inc     hl
        ld      (hl),0          ; Put in terminating null

        ld      a,0ffh          ; Fool WS into thinking shell installed
        ld      (zflag),a
        jp      60aah

endaddr defl    60aah - offset
free    defl    endaddr - end1pat
         if     $ gt endaddr
        ERROR: Patch to shell installation at 3CBFh is too long


; This patch takes the user's response to the 'R' command, adds the command
; to reinvoke WordStar, and appends any pending commands in the command line
; buffer.  The result is written out to the command line buffer.  This
; implements a ZCPR2-style shell for the 'R' command.

; If the resulting command line is too long for the MCL, an error message is
; displayed until a key is pressed, and then WS resumes as if no command line
; had been entered.

; The first part of this patch replaces code in WS.OVR.  There is not enough
; space there for all the code, so it continues in the user patch area.

offset  defl    -1e00h          ; Real address = address in overlay - offset

scratch equ     0a000h          ; Area to use as scratch buffer

        org     67b2h           ; Address in WS.OVR

        ld      hl,rcmdbuf      ; Point to 'R' command buffer
        ld      c,(hl)          ; Get length into BC
        ld      b,0
        inc     hl              ; Point to user's command
        ld      de,scratch      ; Scratch buffer in RAM
        ldir                    ; Copy user's command to buffer

        ld      hl,namebuf      ; Point to WS4 reinvocation command line
        call    cpy2nul         ; Copy through ending null
        jp      morpat          ; Continue in patch area

free    defl    67cbh - end2pat
         if     $ gt 67cbh
        ERROR: Patch to command-line code at 67B2h is too long

;                       PATCHES TO WS.COM

; This is the continuation of the patch in WS.OVR that inserts the user's
; command line, together with the WS reinvocation command, into the multiple
; command buffer.

        org     morpat

        push    de              ; Save pointer to buffer
        ld      e,18h           ; Get pending commands from MCL
        call    envoff
        ld      e,(hl)          ; Get pointer to next command into DE
        inc     hl
        ld      d,(hl)
        ex      de,hl           ; Switch into HL as source for copy
        pop     de              ; Destination pointer into buffer
        call    cpy2nul         ; Copy through ending null

        ld      hl,clrscr       ; Clear the screen
        call    scrnfn

        ld      e,18h           ; Get MCL pointer
        call    envoff          ; HL -> MCL buffer, A = max characters

        ; Check length of new command

        ld      de,scratch      ; Point to new command line
        ld      b,a             ; Max length in B
        ld      a,(de)
        or      a
        jr      z,oklength
        inc     de
        djnz    lenloop

        ld      de,errmsg       ; Display error message
        ld      c,9
        call    0005h
        call    sak             ; Wait for key to be pressed
        jp      7f4eh           ; Pretend no user input

        ld      de,4            ; Offset to command line in buffer
        ex      de,hl           ; Reverse pointers
        add     hl,de
        ex      de,hl           ; HL = MCL, DE = MCL+4
        ld      (hl),e          ; Set up pointer in MCL
        inc     hl
        ld      (hl),d
        ld      hl,scratch      ; Source for command line
        call    cpy2nul         ; Copy it in

        jp      13f6h           ; Chain to command line from WS

        db      bell,'MCL Ovfl - press any key...$'

free    defl    namebuf - end3pat
         if     $ gt namebuf
        ERROR: Patch in MORPAT is too long


; This optional patch causes WS4 to use an internal path to locate
; its overlay files.

         if     intpath

        org     0f5fh           ; This is where z3 path location is determined

        call    setpath         ; Call alternative routine
        nop                     ; Must fill 5 bytes

         endif  ;intpath


; Modification to the termination routine that pops the shell stack.

; This patch eliminates the popping of the shell stack on exit from
; WordStar.  The space from the end of this patch to 13f6h is available
; for other uses (40 bytes).

        org     13ceh

        jp      13f6h           ; Exit routine

; This routine copies the string pointed to by HL to the address pointed to by
; DE until a null byte is encountered.  The null byte is copied as well.

        ld      a,(hl)          ; Get source character
        ld      (de),a          ; Put into destination
        or      a               ; Check for null
        ret     z               ; If so, quit
        inc     hl              ; Bump up pointers
        inc     de
        jr      cpy2nul

; Alternative internal path routine

         if     intpath

        ld      hl,path0        ; Point to internal path size
        ld      a,(hl)
        inc     hl              ; Point to actual path
        or      a               ; Set flags

path0:  db      2               ; Allow up to two elements
        db      2               ; Drive (A=1)
        db      4               ; User
        db      0,0             ; Space for another entry
        db      0               ; Terminating null

         endif  ;intpath

free    defl    13f6h - end4pat
         if     $ gt 13f6h
        ERROR: Patch in shell popping code at 13CEh is too long


; Modification to initialization code where WS4 determines if it was
; invoked as a shell.  We have defined a convention where a comma on
; the end of the command line signals WS4 to display its shell-wait
; message and wait for the user to press a key.

        org     1a2fh

        ld      hl,80h          ; Point to command tail
        ld      l,(hl)          ; Get length into L
        set     7,l             ; In effect, add 80h
        ld      a,(hl)          ; Get last character
        cp      ','             ; See if it is a comma
        jr      nz,1a5fh        ; Not a 'shell', so go on ahead

        ld      (hl),' '        ; Get rid of the comma

        ld      de,1b10h        ; Display 'shell wait' message
        ld      c,9             ; (note: message is trashed by other code and
        call    0005            ; ..cannot be called from elsewhere)
        call    sak             ; Wait for key to be pressed

        jr      1a5fh           ; Proceed normally

        ld      e,0ffh          ; Poll console input status
        ld      c,6
        call    0005
        or      a
        jr      z,sak           ; ..until a key is pressed

        ld      e,0dh           ; Echo carriage return
        ld      c,6
        jp      0005

free    defl    1a5fh - end5pat
         if     $ gt 1a5fh
        ERROR: Patch to initialization code at 1A2Fh is too long



[This article was originally published in issue 35 of The Computer Journal, P.O. Box 12, South Plainfield, NJ 07080-0012 and is reproduced with the permission of the author and the publisher. Further reproduction for non-commercial purposes is authorized. This copyright notice must be retained. (c) Copyright 1988, 1991 Socrates Press and respective authors]