Lotus 1-2-3 DOS Development Encyclopedia.

Tavis Ormandy

$Id: a07cf90837a3c4373b82d6724b97593810766af7 $

Everything I know about Lotus 1-2-3 internals.

Note: This document is incomplete.

Notes

I am only interested in the 123R3 series, but this can get confusing.

123R3 was the first version of 123 after the rewrite in C, prior versions were written in assembly. This means that it is not ABI, API or driver compatible with any other major versions.

However, I’m also interested in 123R4D, which is essentially just an update to 123R3 and is binary compatible with addins and drivers. It’s not that simple though, 123R4D (DOS) is an entirely different product than 123R4W (Windows). You might expect them to be API compatible - but they’re not.

However, the 123R4W ADK (Add-in Development Kit) includes an API compatability library that means you can recompile 123R3/123R4D code for 123R4W, so they’re source compatible. You can’t go in the other direction - there is no way to make a 123R4W add-in work on 123R4D.

Addins

There are two major types of Addins. Those with a DLD extension are dynamically loaded libraries. Those with a PLC extension are compiled LPL scripts. LPL was a scripting language developed specifically for 1-2-3 addins, the language later became LotusScript and was used in other Lotus products.

Here is a fun review of the commercial addins available in 1993.

LPL Addons

I am still searching for the LPL compiler, however I have a copy of the book “Lotus Add-In Toolkit Guide” by Robert Ainsbury (ISBN 0880225505) which described the language, has code samples and screenshots of the editor and debugger.

I have some low quality scans from the book if you want to see them.

one two three four

If you do have the Lotus Toolkit Collection or 123R3 Add-In Toolkit, I would very much like to see them. The toolkit collection was available directly from Lotus on CD-ROM, at least the 1995 Q1 release sounds interesting.

I believe Lotus originally charged a small fee for the toolkit, but later changed course and gave it away for free.

Drivers

An addin typically add @functions or other functionality, whereas drivers abstract away some hardware or software details.

Types and Names

There are four types of drivers, DISPLAY, PRINTER, FILE, and LINK.

Drivers must follow a naming convention that 123 uses to determine what type of driver it is. The filename format is L13XYYYY.DLZ

              ┌→ Release 3
     Lotus ←┐ │ ┌→ Name
            ↓ ↓ ↓
            L13XYYYY.DLZ ← Platform, D (DOS) or W (Windows)
             ↑ ↑
      1-2-3 ←┘ └→ Type, D, P, F, or L

If you have a DRV file, that is from an earlier 123 release. If you have a DLD file that does not follow this naming convention, then it is an addin and not a driver. The difference is simply what APIs are exposed when loaded and what exports 123 expects to find.

Using the wrong code or trying to load a driver as an addin will cause 123 to crash, because they all use different APIs.

The purpose of DISPLAY and PRINTER drivers is obvious. FILE drivers allow i/o from new file formats, and LINK drivers allow 123 to interact with a database or other data source.

Drivers are shipped with configuration files in a binary format called a BUNDLE. A bundle contains a short header followed by a series of TLV (Tag, Length, Value) records. This allows some configuration of the driver during installation, such as fonts, colors, charset, resolution, etc.

Note: The BUNDLE related APIs use the prefix BDL.

See the description of VBD files for an example, a VBD is a Video Bundle.

API

I have never seen the 123R3 or 123R4D ADK, so I’ve had to reverse engineer it.

Sources of information

I converted the adk hlp file to html, if you’re interested.

Here is the driver, and I’ve dumped the CodeView data.

Here is the lucky addin, and I’ve dumped the stabs.

Mirror available Here

I do have the R9 (I think?) ADK, it’s of no interest to me but maybe someone else wants it.

Others

I do not have these, but would really like to see them.

  • The Lotus Toolkit Collection Q1 1995 CD-ROM.
  • The book “Customizing 1-2-3 Release 3” by Barton Listick, ISBN 0201523248.
  • Presumably there was some kind of “driver” development kit shipped to OEMs.
  • Any 123R3 builds for non-DOS platforms.

In particular, I would love to see the SunOS or SCO (aka SCO Professional) builds.

I’m aware of a few other ports, but I’m not sure how relevant they are to 123R3, e.g.

  • OpenVMS
  • Xenix
  • System/390

Siemens Highprint 7400

The file L13PSI74.DLD is a third-party print driver presumably developed by Siemens. I found a copy on a shareware archive, and was surprised to find it had some embedded CodeView data.

It was an ancient verion that no modern tools could parse, I even tried old tools like TDUMP, CVDUMP and and SYMDEB. I did eventually find an old debugger that could dump it. Unfortunately, it was quite a challenge extracting the output - it had no way to save output to a file except “printing” your backlog, and the backlog wasn’t big enough to hold all the data!

I had to script a DOS emulator to scrape the data out a page at a time! I can’t tell you how much time I wasted getting this working, but it was worth it, it gave some valuable hints about how the DEVPRIM API worked.

XALERT addin for SunOS

The Lotus FTP server had a few official addins still available to download after all these years, and amazingly one was mistakenly compiled with very detailed stabs data.

That addin was alert.so1, the XALERT addin for SunOS4. It’s a SPARC demand paged shared library, which is a long-obsolete format.

I was able to find an older version of GNU binutils that still supported a.out-sunos-big, and could dump it.

The structure names and enums were remarkably helpful, even though it’s for an entirely different platform.

123R4W Addin Development Kit

I sadly do not have the 123R4D or 123R3 development kits, but I do have the 123R4W kit. This includes headers, libraries and a comprehensive HLP library.

123R4W Directory Layout…

.
├── [        512 Feb 16 07:17]  BINB
│   └── [     228080 Jun 10  1993]  LRC.EXE
├── [        512 Feb 16 07:26]  HELP
│   ├── [      20928 Feb 20 18:44]  ADKREF.GID
│   ├── [     611855 Jul 26  1993]  ADKREF.HLP
│   └── [        766 Dec 27  1992]  ADKREF.ICO
├── [        512 Feb 20 22:41]  INCLUDE
│   ├── [      10788 Jul 29  1993]  GUITYPES.RC
│   ├── [      23469 Jul 29  1993]  KEYCODE.H
│   ├── [       3372 Jul 29  1993]  LCIAF.H
│   ├── [      23400 Jul 29  1993]  LCIATFNC.H
│   ├── [      11962 Jul 29  1993]  LCICELL.H
│   ├── [      10909 Jul 29  1993]  LCICOMN.H
│   ├── [      19826 Jul 29  1993]  LCIENUM.H
│   ├── [       7873 Jul 29  1993]  LCIERROR.H
│   ├── [       4487 Jul 29  1993]  LCIEVENT.H
│   ├── [      26115 Jul 29  1993]  LCIEVENT.T
│   ├── [       8956 Jul 29  1993]  LCIGUI.H
│   ├── [       3534 Jul 29  1993]  LCILD.H
│   ├── [       6465 Jul 29  1993]  LCIMATH.H
│   ├── [       8656 Jul 29  1993]  LCIMB.H
│   ├── [        902 Jul 29  1993]  LCIMBLIB.H
│   ├── [       2332 Jul 29  1993]  LCIMC.H
│   ├── [      11499 Jul 29  1993]  LCIRANGE.H
│   ├── [       5823 Jul 29  1993]  LCISHEET.H
│   ├── [       5265 Jul 29  1993]  LCIUTIL.H
│   ├── [       9116 Jul 29  1993]  LCIWFILE.H
│   ├── [      12990 Jul 29  1993]  LCIWKSPC.H
│   ├── [       1390 Jul 29  1993]  LPIMEMM.H
│   ├── [       9335 Jul 29  1993]  LPIMEMM.T
│   ├── [       9568 Jul 29  1993]  LPIPRIM.H
│   ├── [       2399 Jul 29  1993]  LPIXPORT.H
│   ├── [      44708 Jul 29  1993]  LPXATFNC.H
│   ├── [      19643 Jul 29  1993]  LPXCELL.H
│   ├── [       1781 Jul 29  1993]  LPXCOMN.H
│   ├── [      20236 Jul 29  1993]  LPXDEFS.H
│   ├── [      12832 Jul 29  1993]  LPXENUM.H
│   ├── [       7590 Jul 29  1993]  LPXERROR.H
│   ├── [       1959 Jul 29  1993]  LPXMACRO.H
│   ├── [       4966 Jul 29  1993]  LPXMATH.H
│   ├── [      23952 Jul 29  1993]  LPXRANGE.H
│   ├── [      11743 Jul 29  1993]  LPXSHEET.H
│   ├── [       2423 Jul 29  1993]  LPXUTIL.H
│   ├── [      18223 Jul 29  1993]  LPXWFILE.H
│   ├── [      17015 Jul 29  1993]  LPXWKSPC.H
│   ├── [      20055 Jul 29  1993]  LRCDEFS.H
│   ├── [       5704 Jul 29  1993]  LTSCNTRL.H
│   ├── [      21294 Jul 29  1993]  LTSDEFS.H
│   ├── [       3876 Jul 29  1993]  LTSSTRCT.H
│   └── [      47128 Jul 29  1993]  WINRESID.H
├── [        512 Feb 19 16:03]  LIB
│   ├── [      67072 Jul 29  1993]  LCI.LIB
│   ├── [       3558 Jun 10  1993]  LIBMAIN.OBJ
│   ├── [      16384 Jun 10  1993]  LMBSRVW.LIB
│   ├── [      72704 Jun 10  1993]  LPIMISC.LIB
│   └── [       3408 Jun 10  1993]  WEP.OBJ
├── [      12341 Jul 27  1993]  READ.ME
└── [        512 Feb 19 16:04]  SAMPLES
    ├── [        512 Feb 16 07:16]  ADDMENU
    │   ├── [        485 Jul 29  1993]  ADDMENU.BMK
    │   ├── [      15944 Jul 29  1993]  ADDMENU.C
    │   ├── [        568 Jul 29  1993]  ADDMENU.DEF
    │   ├── [       2237 Jul 29  1993]  ADDMENU.H
    │   ├── [       2384 Jul 29  1993]  ADDMENU.MAK
    │   ├── [       1022 Jul 29  1993]  ADDMENU.NMK
    │   └── [       1606 Jul 29  1993]  ADDMENU.RC
    ├── [        512 Feb 16 07:16]  ADNDLG
    │   ├── [        528 Jul 29  1993]  ADNDLG.BMK
    │   ├── [      12134 Jul 29  1993]  ADNDLG.C
    │   ├── [        566 Jul 29  1993]  ADNDLG.DEF
    │   ├── [       2585 Jul 29  1993]  ADNDLG.H
    │   ├── [       2439 Jul 29  1993]  ADNDLG.MAK
    │   ├── [       1161 Jul 29  1993]  ADNDLG.NMK
    │   └── [       3157 Jul 29  1993]  ADNDLG.RC
    ├── [        512 Feb 16 07:16]  ADNMDI
    │   ├── [        528 Jul 29  1993]  ADNMDI.BMK
    │   ├── [      11067 Jul 29  1993]  ADNMDI.C
    │   ├── [        578 Jul 29  1993]  ADNMDI.DEF
    │   ├── [       2451 Jul 29  1993]  ADNMDI.H
    │   ├── [       2439 Jul 29  1993]  ADNMDI.MAK
    │   ├── [       1159 Jul 29  1993]  ADNMDI.NMK
    │   └── [       3539 Jul 29  1993]  ADNMDI.RC
    ├── [        512 Feb 16 07:17]  AFHELLO
    │   ├── [        633 Jul 29  1993]  AFHELLO.BMK
    │   ├── [       4577 Jul 29  1993]  AFHELLO.C
    │   ├── [        552 Jul 29  1993]  AFHELLO.DEF
    │   ├── [       2233 Jul 29  1993]  AFHELLO.MAK
    │   └── [       1010 Jul 29  1993]  AFHELLO.NMK
    ├── [        512 Feb 16 07:17]  AMORT
    │   ├── [        607 Jul 29  1993]  AMORT.BMK
    │   ├── [      19650 Jul 29  1993]  AMORT.C
    │   ├── [        548 Jul 29  1993]  AMORT.DEF
    │   ├── [       2259 Jul 29  1993]  AMORT.MAK
    │   └── [       1004 Jul 29  1993]  AMORT.NMK
    ├── [        512 Feb 16 07:17]  COLORS
    │   ├── [        652 Jul 29  1993]  COLORS.BMK
    │   ├── [      20170 Jul 29  1993]  COLORS.C
    │   ├── [        578 Jul 29  1993]  COLORS.DEF
    │   ├── [       5049 Jul 29  1993]  COLORS.H
    │   ├── [        766 Oct  8  1991]  COLORS.ICO
    │   ├── [       2439 Jul 29  1993]  COLORS.MAK
    │   ├── [       1167 Jul 29  1993]  COLORS.NMK
    │   └── [       8030 Jul 29  1993]  COLORS.RC
    ├── [        512 Feb 16 07:17]  DEMO
    │   ├── [       5632 Jul 29  1993]  ADDMENU.ADW
    │   ├── [      37316 Jul 22  1993]  ADKDEMO.WK4
    │   ├── [       6144 Jul 29  1993]  ADNDLG.ADW
    │   ├── [       8704 Jul 29  1993]  ADNMDI.ADW
    │   ├── [       2784 Jul 29  1993]  AFHELLO.ADW
    │   ├── [       5376 Jul 29  1993]  AMORT.ADW
    │   ├── [      15360 Jul 29  1993]  COLORS.ADW
    │   ├── [       8704 Jul 29  1993]  FILEMEMO.ADW
    │   ├── [       7680 Jul 29  1993]  LMBCS.ADW
    │   ├── [       2784 Jul 29  1993]  MCHELLO.ADW
    │   ├── [       3792 Jul 29  1993]  MYSUM.ADW
    │   ├── [       2768 Jul 29  1993]  NAVIGATE.ADW
    │   ├── [       3536 Jul 29  1993]  UNDOEX.ADW
    │   ├── [      12800 Jul 29  1993]  WECYCLE.ADW
    │   └── [       2752 Jul 29  1993]  WIDTHX2.ADW
    ├── [        512 Feb 16 07:17]  FILEMEMO
    │   ├── [        686 Jul 29  1993]  FILEMEMO.BMK
    │   ├── [      26423 Jul 29  1993]  FILEMEMO.C
    │   ├── [        626 Jul 29  1993]  FILEMEMO.DEF
    │   ├── [       1308 Jul 29  1993]  FILEMEMO.H
    │   ├── [       2850 Jul 29  1993]  FILEMEMO.MAK
    │   ├── [       1177 Jul 29  1993]  FILEMEMO.NMK
    │   └── [       3029 Jul 29  1993]  FILEMEMO.RC
    ├── [       1696 Jul 29  1993]  LIBMAIN.C
    ├── [        512 Feb 16 07:17]  LMBCS
    │   ├── [        511 Jul 29  1993]  LMBCS.BMK
    │   ├── [      12274 Jul 29  1993]  LMBCS.C
    │   ├── [        597 Jul 29  1993]  LMBCS.DEF
    │   ├── [       4279 Jul 29  1993]  LMBCS.H
    │   ├── [       2406 Jul 29  1993]  LMBCS.MAK
    │   ├── [       1154 Jul 29  1993]  LMBCS.NMK
    │   └── [       4237 Jul 29  1993]  LMBCS.RC
    ├── [        512 Feb 16 07:17]  MCHELLO
    │   ├── [        633 Jul 29  1993]  MCHELLO.BMK
    │   ├── [       5933 Jul 29  1993]  MCHELLO.C
    │   ├── [        552 Jul 29  1993]  MCHELLO.DEF
    │   ├── [       2228 Jul 29  1993]  MCHELLO.MAK
    │   └── [       1010 Jul 29  1993]  MCHELLO.NMK
    ├── [        512 Feb 16 07:17]  MYSUM
    │   ├── [        607 Jul 29  1993]  MYSUM.BMK
    │   ├── [      10805 Jul 29  1993]  MYSUM.C
    │   ├── [        548 Jul 29  1993]  MYSUM.DEF
    │   ├── [       2445 Jul 29  1993]  MYSUM.MAK
    │   └── [       1004 Jul 29  1993]  MYSUM.NMK
    ├── [        512 Feb 16 07:17]  NAVIGATE
    │   ├── [        646 Jul 29  1993]  NAVIGATE.BMK
    │   ├── [       3804 Jul 29  1993]  NAVIGATE.C
    │   ├── [        552 Jul 29  1993]  NAVIGATE.DEF
    │   ├── [       2240 Jul 29  1993]  NAVIGATE.MAK
    │   └── [       1013 Jul 29  1993]  NAVIGATE.NMK
    ├── [        512 Feb 16 07:17]  UNDOEX
    │   ├── [        620 Jul 29  1993]  UNDOEX.BMK
    │   ├── [       6751 Jul 29  1993]  UNDOEX.C
    │   ├── [        560 Jul 29  1993]  UNDOEX.DEF
    │   ├── [       2115 Jul 29  1993]  UNDOEX.MAK
    │   └── [       1007 Jul 29  1993]  UNDOEX.NMK
    ├── [        512 Feb 16 07:17]  WECYCLE
    │   ├── [        370 Jul 21  1993]  TRUCK.BMP
    │   ├── [        766 Sep  2  1991]  TRUCK.ICO
    │   ├── [        691 Jul 29  1993]  WECYCLE.BMK
    │   ├── [      19406 Jul 29  1993]  WECYCLE.C
    │   ├── [        584 Jul 29  1993]  WECYCLE.DEF
    │   ├── [       2066 Jul 29  1993]  WECYCLE.H
    │   ├── [       2984 Jul 29  1993]  WECYCLE.MAK
    │   ├── [       1180 Jul 29  1993]  WECYCLE.NMK
    │   └── [      13859 Jul 29  1993]  WECYCLE.RC
    ├── [       1545 Jul 29  1993]  WEP.C
    └── [        512 Feb 16 07:17]  WIDTHX2
        ├── [        633 Jul 29  1993]  WIDTHX2.BMK
        ├── [       4050 Jul 29  1993]  WIDTHX2.C
        ├── [        529 Jul 29  1993]  WIDTHX2.DEF
        ├── [       1969 Jul 29  1993]  WIDTHX2.MAK
        └── [       1010 Jul 29  1993]  WIDTHX2.NMK

20 directories, 158 files

Compatability Library

The 123R4W ADK uses the Lci symbol prefix, 123R4D uses Lpi. There is a library that translates many functions.

Display Driver API

The display driver requires the following function exported by ordinal. Note that I do not know the names of any of these functions, structures or types - I have guessed them from reverse engineering what 123 is doing.

Also, expect mistakes or incomplete information - I’ve never seen any documentation!

Note: If you do find an error, I would like to hear about it!

A working implementation of a 123R4D driver is available here, it might be a useful reference if you’re trying to get 123 work somewhere new.

1. GetDisplayInfo

This function simply copies a struct DISPLAYINFO into the far pointer provided. 123 uses this to learn the display charateristics. The DISPLAYINFO struct is given below.

Note: DriverInit() is called before GetDisplayInfo(), even though it’s declared first.

The prototype is:

2. DriverInit

This function is called as soon as the device is loaded, you can use this function to initalize any data structure you want, parse configuration data, etc.

The prototype is something like:

Return 0 if you successfully initialized, or 1 if there was an error.

Param Description
deflmbcsgrp The LMBCS code group to use with optimized LMBCS.
cfgname The filename of the BDL configuration data.
vbdptr Pointer to your BDL data.
drvapi Array of function pointers used to interact with 123.

The vbdptr is a BDLHDR, followed by a sequence of BDLRECHDRs.

Before this routine completes, you should use RegisterDevData to report device capabilities and graphics callbacks to 123.

3. DriverTerminate

This function is called when you are being unloaded, you can use it to cleanup.

4. ResetDisplay

This function is called when 1-2-3 exits graphics mode (e.g after the user closes a graph).

You would typically use it to switch back to text mode. The screen should be blank, ready to write text, and the cursor should be at 0,0 when complete.

5. SetGraphicsMode

Prepare the screen for graphics, e.g. when the user presses F10.

The screen should be blank and ready to accept drawing commands (e.g. fill_rect, thin_line, etc) when this returns.

6. MoveCursor

Move the internal cursor to the specified position, you do not have to adjust the position of the visible cursor - only where the next screen write will occur.

Param Description
line Move the cursor to this line.
col Move the cursor to this col.

7. WriteLmbcsStringWithAttributes

A pointer to an LMBCS string is specified with color attributes, it should be translated to the users current charset, and printed to the current cursor location.

Param Description
attrs The requested color attributes.
lmbcsptr Far pointer to an LMBCS string.
byteslen Length of lmbcsptr in bytes to be translated.

Screen Attributes

The attributes are made of 6 bits, 3 bits representing the foreground class and 3 bits representing the background class. These don’t represent any specific colors - just types, you can draw them however you want.

8. WritePaddedLmbcsStringWithAttributes

This is similar to WriteLmbcsStringWithAttributes, but you can specify padding to apply to either side of the string. The attributes specified are applied to the padding and the string.

Using SPC characters for padding is fine.

Param Description
endpad How much padding is required after the content.
startpad How much padding is required before the content.
attrs The requested color attributes.
lmbcsptr Far pointer to an LMBCS string.
byteslen Length of lmbcsptr in bytes to be translated.

9. SetRegionBgAttributes

Change the bg attributes of a screen region, leaving the contents intact. The region starts at the current cursor.

For example, this might be used to change the background color.

Note that the text and fg attributes are unchanged.

As far as I can tell, 123 will never request an invalid column or line number, i.e. you are not expected to handle line wrap or off-screen drawing.

Param Description
attrs The requested color attributes.
numlines How many lines to apply this change to.
endcol On the last line, where should the region stop.

10. ClearRegionForeground

Remove the foreground content (i.e. write blanks over it and clear fg attributes). New background attributes can be specified.

Param Description
attrs The requested color attributes.
numlines How many lines to apply this change to.
endcol On the last line, where should the region stop.

11. ClearRegionForegroundKeepBg

The same as ClearRegionForeground, but the existing bg attributes are retained.

Param Description
numlines How many lines to apply this change to.
endcol On the last line, where should the region stop.

12. BlockRegionCopy

Copy a rectangular, including attributes, from one screen area to another.

The regions may overlap, and you’re expected to handle that.

Param Description
width Number of columns of the region.
height Number of lines.
dstx The destination x position, the source is given by the cursor.
dsty The destination y position.

13. CalcSizeTranslatedString

You should translate the passed LMBCS string into the current character set, and then return the length. You don’t have to display anything.

Param Description
argstr The LMBCS string to translate.
argstrlen Length of the string.

14. FitTranslatedString

Lotus has maxlen columns to display an LMBCS string, so you should translate it and truncate it (if necessary) to fit.

Param Description
dstptr Write the translated & truncated string here.
ncols The maximum length the translated string should be.
argstr The LMBCS string to translate.
argstrlen Length of the string.

15. SetCursorInvisible

The screen cursor should no longer be visible to the user, perhaps because they are navigating a menu.

16. SetCursorVisible

The user is entering data so the screen cursor should be visible. The position should match the internal cursor, as set by MoveCursor.

17. LockCursorAttributes

Not really sure why this is useful, or what the intention is. I believe it ignores changes to cursor attributes until turned off.

18. Unknown

This is called before entering graphics mode, and appears to be related to querying resolution data.

19. DrawString

This appears to be related to setting graph legends in graphics mode.

20. MoveCursor2

This appears to be identical to MoveCursor().

21. IsGraphicsModeReady

When entering graphics mode, this function is called repeatedly until it returns 1. Perhaps the intention is to query if the hardware is ready to display graphics.

Printer Driver API

The display driver API is quite complicated, the printer driver API is simple in comparison. Even better, it’s essentially just a tiny subset of the DISPLAY API (the DEVPRIM part).

File Driver API

TODO

I don’t know very much about the FILE API, but if it means I could write a driver that allows Lotus 1-2-3 for DOS to open modern XLS or ODS files, then that sounds like the kind of ridiculous project I would be interested in!

If you didn’t already know this, LibreOffice can open Lotus WK3 files just fine.

Native Add-In API

TODO

LPL Add-In API

TODO

Other Extension Languages

The OS/2 port had an official REXX extension API.

Building Native Add-Ins

TODO