This Week With My Coleco ADAM 9710.20

by Richard F. Drushel (drushel@apk.net)

I. Status Report: SmartWriter Disassembly.

I'm happy to report that yesterday I finished my complete disassembly and reverse-engineering to assemblable source code of SmartWriter R80. This was done using Kenneth Gielow's Z80DIS 2.2 under the Z80MU CP/M emulator for MS-DOS. The new source is formatted for the Z80ASM+ assembler from SLR. The new source (minimally commented; it has all the EOS global symbols in it, as well as a few things I added on my own) is available for download:

This file is a PKZIP 2.04g compressed version of the ASCII plaintext source. Note that it has no bugfixes or changes; it will exactly regenerate the original SmartWriter R80 binary, warts and all. Of course, now this source code can serve as the basis for bugfixes/upgrades to SmartWriter (including making it XRAM-friendly for a possible XRAM-based EOS).

II. Verifying the New Source Code.

How can I be sure that I correctly reverse-engineered the source code? The new SmartWriter source was verified in two ways:

  1. the new source was reassembled to a 32K binary image using Z80ASM+, then compared byte-by-byte with the original binary image of the R80 SmartWriter ROM. Except for padding out the unused ROM space with 00H instead of 0FFH, my new binary was identical to the old.

  2. the new source was modified by inserting a NOP instruction after the first instruction, reassembled to a binary image, then tested using Marcel de Kogel's ADAM Emulator for MS-DOS, ADAMEM.EXE. The effect of the NOP is to displace all the code forward by one byte, without affecting the program operation (NOP means No OPeration, it doesn't do anything). Extensive testing of the new binary revealed no operational errors.

What is the rationale for (2)? In the code, there are many references to ROM data areas (e.g., message strings, sprite patterns), which of course are to the addresses in ROM at which those data sit. In a typical program, executable code and non-executable data are intermingled in the binary image. Since a disassembler program doesn't know anything about the internal structure of the binary it's disassembling, data areas often appear as "garbage" machine code instructions in the raw disassembly output. It is the task of the human disassembler to recognize such "garbage" code and edit the listing to properly mark it as data. Any data references which aren't caught, however, will no longer point to the proper place if the code length changes in editing, and the data doesn't end up in exactly the same place in ROM that it originally occupied. Thus, inserting a NOP to shift the reading frame of the code, then testing the new binary for errors, will reveal any missing data references as program bugs.

The initial NOP-shifted binary did not boot at all--I got a black screen. Clearly, I had missed some data references. I then started from the end of the program and worked forward, inserting a NOP, reassembling, and then retesting. NOPs inserted towards the end allowed the binary to boot, but the screen was a bit scrambled. One bug at a time--track down what kept it from booting first, and worry about the screen garbage later.

By repeated NOP-shifting, always bisecting the interval between what would boot and what gave the black screen, I found the place in the code where I had missed a data reference. In the code which loaded EOS from ROM to high RAM, the start address of the ROM was listed as "program label 6000H" instead of just the number 6000H. Any NOP inserted after 6000H wouldn't cause a problem, but a NOP before 6000H would cause "program label 6000H" to have the *value* 6001H--which would cause the whole of EOS to be copied to the wrong location in RAM, and thus crash as soon as any EOS function call was attempted. Fixing this in the new source allowed SmartWriter to boot--but with a scrambled screen, as noted above.

The scrambled-screen problems were due to references to a few video RAM (VRAM) addresses which happened to have the same numeric value as some program labels. (The disassembler just sees the final number, and if there is a program label with that number, it substitutes that label name in the code.) During my initial cleanup of the raw disassembly listing, I had recognized and fixed a few of these anyway, from my familiarity with how VRAM is typically used; but some others less obvious slipped through the cracks. Again, it was repeated NOP-shifting (always bisecting the interval between a shift that "broke" the program and one which had no effect) which allowed me to zero in on the references to program labels which needed to be changed into numbers. (The strategy is exactly like that in the "higher-lower" game, in which one person thinks of a number, and a second person tries to guess it, the first person answering only "higher" or "lower" depending upon the accuracy of the guesses.)

When the screen was unscrambled, there remained a small glitch: in the ADAM'S ELECTRONIC TYPEWRITER mode, the message string for SmartKey V was off by a few letters if I NOP-shifted the code at the beginning. Using the same procedure described above, I zeroed in on the offending part of the code, but couldn't see what was causing the problem: the critical address was the very address of the particular SmartKey V message string!

To make a long story short, the problem was that I had not correctly separated code from data areas in the subroutine which draws the SmartKey menus for the ELECTRONIC TYPEWRITER. In SmartWriter, SmartKey menus are drawn using an economical yet uncongenial-to-disassembly subroutine which includes the address of the string directly after the CALL to the menu- drawing routine. Here's the critical bit of code, correctly resolved:

C.0B07:
      CALL  DRAW_YELLOW_SK_4
;
      DW    I$12FC
;     -----------------
;
      CALL  DRAW_SK_5
;
      DW    I$132D
;     -----------------
;
      CALL  DRAW_SK_6
;
      DW    I$131C
;     -----------------
;
      RET
;
;     -----------------

;[...]

I$12FC:
      DB    ' ADAM''S ',0DH                ;0DH is carriage return
      DB    ' ELECTRONIC TYPEWRITER',03H   ;03H is ETX, end-of-text marker
I$131C:
      DB    ' MARGIN',0DH
      DB    ' RELEASE',03H
I$132D:
      DB    ' MARGIN/',0DH
      DB    'TAB/ETC',03H

The subroutines of the form CALL DRAW_YELLOW_SK_x draw a yellow box from SmartKey I to SmartKey x, with the text of string at the address following the CALL printed in black. The subroutines of the form CALL DRAW_SK_x draw a blue (2 possible shades, depending upon which SmartKey) box in SmartKey x's space, with the text of the string at the address following the CALL printed in white. The DW LABEL$ assembly directive means to store a Data Word containing the address of the program label LABEL$, whatever value LABEL$ has at assembly time.

The first time through the disassembler, however, all these "inline" data references get treated as code. When I first recognized the existence of the DRAW_YELLOW_SK_x and DRAW_SK_x family of functions, I searched through the code listing and (supposedly) changed all the "garbage" code to DW references. In the case of the 'MARGIN/TAB/ETC' string, however, I missed one; and the (2DH, 13H) was treated as the following assembly code:

      CALL  DRAW_SK_5
;
      DEC  L                ;2DH
      INC  DE               ;13H

This meant that, when I NOP-shifted the program anywhere prior to I$132D, the new value of this label was *not* updated in what should have been the data reference after the CALL DRAW_SK_5. The result was that this DRAW_SK_5 would *always* try to draw a SmartKey with whatever data it found at address 132DH, even if the actual message string had shifted to a different absolute address. Needless to say, putting in the correct reference as DW I$132D fixed the problem; and now I could edit the new SmartWriter source at will, without fear of such frameshift errors being introduced by the edits.

III. Code Fossil: ADAMlink Modem I/O in SmartWriter.

My first disassembly of SmartWriter was done in 1992 or thereabouts, using the UNASMHEX disassembler I wrote for SmartBASIC 1.0 and 1.x (and ported to Microsoft BASIC). This disassembler did not make any attempt to generate assemblable source code; but I used it extensively, and it was my primary tool for figuring out how to patch PowerPaint for hard drives, HARDDISK 2.3 and 3.9 for RAMdisks, and ADAMlink IValpha to make ADAMlink V (not to mention SmartBASIC 1.0 itself to make SmartBASIC 1.x). I made a quick pass through SmartWriter just for fun, I guess, and at that time had discovered a truly amazing code fossil: SmartWriter knows about the ADAMlink modem, and tries to talk to it at startup!

I believe that I wrote a brief report of this discovery for A.N.N., but I can't be completely sure. At any rate, during the resourcing of SmartWriter, I came across the code again; so here it is, with some brief explanations:

MODEM_CTRL_PORT   EQU   5EH   ;ADAMlink modem control port (2651 UART)
MODEM_DATA_PORT   EQU   5FH   ;ADAMlink modem data port (2651 UART)

;     -----------------

;Load a program through ADAMlink modem!
;At least that's the intent.  It can't work, however, because
;there is no handshaking for any of the data reads; a 300 bps
;modem can't supply the characters that fast.  Maybe if the
;proper handshaking were added...it would be interesting.

C$103C:
      LD    BC,(D$1071)
      LD    A,B
      OUT   (C),A               ;send first init command
      LD    BC,(D$1073)
      LD    A,B
      OUT   (C),A               ;send second init command
      LD    A,(D$1075)
      LD    C,A                 ;get data port
      IN    A,(C)               ;read for first magic number
      CP    0AAH                ;was it magic1?
      JP    NZ,J.1070           ;no, so exit
;
      IN    A,(C)               ;yes, so read second magic number
      CP    55H                 ;was it magic2?
      JP    NZ,J.1070           ;no, so exit
;
      IN    E,(C)               ;yes, so get DE=count
      IN    D,(C)
      IN    L,(C)               ;get HL=load address
      IN    H,(C)
      PUSH  HL                  ;save start vector
J$1065:
      IN    A,(C)               ;read a byte
      LD    (HL),A              ;save it
      INC   HL                  ;point to next address
      DEC   DE                  ;one less count
      LD    A,E
      OR    D                   ;is D=E=0?
      JR    NZ,J$1065           ;no, so keep going 'til done
;
      POP   HL                  ;yes, so restore load address
      JP    (HL)                ;and jump to program start
;
;     -----------------
J.1070:
      RET                       ;exit
;
;     -----------------

;initialization data for ADAMlink modem

D$1071:
      DB    MODEM_CTRL_PORT,00H     ;port, data
D$1073:
      DB    MODEM_CTRL_PORT,80H     ;port, data
D$1075:
      DB    MODEM_DATA_PORT         ;port
;     -----------------

As noted in the code comments, there's no way that this can work with the ADAMlink modem as we now have it; there's no handshaking for any of the modem data port reads. So, if you try to set this up now, with a second computer ready to send a program at 300 bps, it will fail. Now that I have reassemblable source for SmartWriter, though, it might be worth trying to make this code workable, just for kicks.

Why is this routine in here, to load and run a file over the ADAMlink modem? At this late date, only the SmartWriter programmers themselves could answer definitively; but I will offer this intriguing speculation as food for thought:

What if Coleco planned to start a nationwide BBS service, like CompuServe? They would have support areas for the ColecoVision and the ADAM, news about upcoming hardware and software, etc. Presumably the only way you could login would be via an ADAMlink modem (there were no third-party ADAMlink modems or serial interface boards to use standard Hayes-type modems). What if you wanted to try out a demo version of some new ColecoVision game or ADAM program, while you were on-line? In the appropriate forum, you could issue a command to start downloading a program (after making sure that your ADAMlink boot tape or disk was out of the drive), then pull the reset switch on your ADAM. Since you're still on-line, and loader code in SmartWriter doesn't hang up the phone, you could start receiving the file. This loader would grab the file, put it in RAM at the appropriate place, then run it. Presumably, the demo program would have a software escape mechanism to reboot the machine under ADAMlink so you could continue your online session. And this would be "safe" for Coleco because there would be no way for the users to save the program they had downloaded, because it wasn't downloaded to disk or tape, but rather directly to RAM, from which there is no escape (the ADAM not having a built-in debugger or program tracer).

In many respects, the Coleco ADAM is to the 1980s what TV-based "network computers" are to the 1990s. Technically, however, there is no way that 300 bps could have efficiently transmitted multimedia-type data. Even a 4K demo program would take over 2 minutes to download at 300 bps 8N1. I predict that "network computers" will be as commercially successful as the ADAM...

IV. Possible Repair of SmartWriter R80 Bugs.

Now that SmartWriter can be changed at the source level (meaning, repairs are easy and can be as extensive as desired), you should all send me your lists of long-standing bugs to fix. The one that I remember is the inability to access disk 2 (drive D). I know where in the code the tape/disk detects are, and it looks to be a straightforward fix to add support for disk 2. (I may even start playing with this tonight, after my kids are in bed.) I also seem to recall something to do with there being an extra linefeed or half-linefeed at the bottom of pages during printing; if someone can send me a detailed description of how to produce this bug, I'd be grateful).

From the PRINT menu, there is an unused SmartKey menu entry for PRINT FILE. Making it print A-type (plain ASCII) files is trivial; making it interpret an existing SmartWriter file without actually loading it in and printing it from the workspace is probably a lot harder, but doable (I haven't looked).

Also, it might be nice to have native support for the PIA2-type parallel printer card (instead of having to run FASTPATCH or some equivalent). Again, adding the driver code is easy--and I've even published the core parallel printer driver in a previous TWWMCA (070907). Of course, anything requiring specific escape codes for parallel printers is out. A 132-column mode would probably involve deciphering and documenting SmartWriter's RAM data tables above 8000H, which I haven't done and wouldn't particularly relish doing.

It is also very easy to disable the XRAM detect routine, which would prevent SmartWriter from recognizing XRAM at all, thus making it more friendly to future XRAM-based operating system upgrades. If disk 2 support is being added, it probably would be worth adding RAMdisk (device 26) support at the EOS level (leaving it to the EOS RAMdisk driver to worry about how to manage the different memory bank switches that would be necessary).

So, send me your bug lists for SmartWriter! There are more than 3751 bytes free at the end of the 32K ROM to play with, which is lots of space in Z80 machine code!

See you next week!

*Rich*


Next Article
Previous Article
TWWMCA Archive Main Page