https://lock.cmpxchg8b.com/lotus123.html
Lotus 1-2-3
Tavis Ormandy
$Id: a07cf90837a3c4373b82d6724b97593810766af7 $
* Intro
* Problem
+ Research
o Endeavor
+ Reverse Engineering
+ Archives
o History
o More Luck
* Hello World
* Results
* Conclusion
Intro
1-2-3 Screenshot
I tend to use a lot of retro software, partly because I'm a big nerd
but also because I just prefer to work in a terminal. There were a
ton of major commercial software products for MS-DOS that were highly
polished, and can still be used effectively in an xterm today.
I do like using Lotus 1-2-3, I even have a boxed copy of the last DOS
version released, version 4!
Problem
1-2-3 Broken Display
The truth is I'm cheating, it does work, but it only supports a few
standard text mode resolutions. If your terminal is not exactly 80
columns wide, it just makes a big ugly mess on your screen.
There's a workaround, just type stty cols 80, and it will be confined
to a portion of your terminal, looking a bit sad. There is no way to
display more columns, and maximizing your terminal will do nothing.
Research
I wondered if other people still uses 123, and if they had found a
solution. I enjoy using it, but being limited to such a small work
area is a real nuisance.
I spent an hour reading vintage computing forums and old USENET
posts.
Unfortunately, the answer is that nobody has a solution to my
resolution problem, but I did find something interesting...
Endeavor
Epson Manual
I found this old manual for a 1993 PC Workstation called the Epson
Endeavor, which claimed to be distributed with a utilities disk that
enabled a 100x31 text mode in 1-2-3.
How could such a thing be possible?
I was able to find the drivers on an old ftp site. Sadly they didn't
work, presumably they needed a real Cirrus Logic VGA card, but the
fact that these drivers exist at all made me curious. It seems
plausible that 123 can internally generate an arbitrary sized canvas,
and it simply asks the display driver what resolution it wants?
I know that dosemu can simulate arbitrary text mode resolutions, and
some DOS software is able to use it. Is it possible that I could
write a dosemu display driver, and 123 would just work in a
fullscreen xterm?
Epson Endeavor
Reverse Engineering
Analyzing Configuration Files
The display driver API is unfortunately quite complicated, I think it
might take a while to figure out with just a disassembler and a
debugger. Further complicating matters, 123 uses a technique called
overlays (like software bank switching) which confuses the debugger
I'm using as it loses breakpoints whenever a overlay switch occurs!
I figured that Lotus must have sent Cirrus Logic some documentation
to write the driver, perhaps that still exists somewhere and would
save me some time.
I spent some time looking, but came up empty.
I learned a lot about the Lotus add-in ecosystem, and how they
worked. I figured I would download all the drivers and add-ins I
could find, I'll start reversing the simplest one, and move on until
I know enough to write my own.
If you want to read about what commercial addins were available, this
article is a fun read.
Archives
Believe it or not, the old Lotus ftp site is still online. I mirrored
everything they had to my workstation and sifted through it.
Then I got a lucky break...
History
Lotus 1-2-3 was ported to a bunch of systems, including OpenVMS,
Xenix, and even System/390. In 1991, Lotus released a version for
SunOS4 on SPARC.
A few years later, a Lotus engineer uploaded a patch for a file
management bug in the XALERT component that affected the 1.2 update.
Luckily, they forgot to remove the STABS data, leaving incredibly
rich debugging data.
typedef short int lmbcs_type;
typedef short int platform;
typedef short int memory_type;
struct envblk_ { /* size 68 id 9 */
short int envsize; /* bitsize 16, bitpos 0 */
short int pfid; /* bitsize 16, bitpos 16 */
short int csid; /* bitsize 16, bitpos 32 */
short int pad; /* bitsize 16, bitpos 48 */
short int maps; /* bitsize 16, bitpos 64 */
short int type; /* bitsize 16, bitpos 80 */
mptr nulh; /* bitsize 32, bitpos 96 */
long unsigned int maxsize; /* bitsize 32, bitpos 128 */
unsigned char *applid; /* bitsize 32, bitpos 160 */
lhdl gabinfo; /* bitsize 32, bitpos 192 */
char *regfile; /* bitsize 32, bitpos 224 */
mptr (*get) (/* unknown */); /* bitsize 32, bitpos 256 */
short int (*free) (/* unknown */); /* bitsize 32, bitpos 288 */
lhdl (*map) (/* unknown */); /* bitsize 32, bitpos 320 */
void (*unmap) (/* unknown */); /* bitsize 32, bitpos 352 */
lhdl (*alloc) (/* unknown */); /* bitsize 32, bitpos 384 */
short int (*dealloc) (/* unknown */); /* bitsize 32, bitpos 416 */
short unsigned int (*load) (/* unknown */); /* bitsize 32, bitpos 448 */
short unsigned int (*unload) (/* unknown */); /* bitsize 32, bitpos 480 */
short unsigned int (*syscall) (/* unknown */); /* bitsize 32, bitpos 512 */
};
typedef struct envblk_ /* id 9 */ envblk;
typedef long int LpiSTATUS;
...
This was the only file (trust me, I checked them all) that had any
debugging data left on the whole ftp!
Now, this file was for a different architecture, operating system,
compiler, release and was a long obsolete format. But, the clues it
contained about the driver API were invaluable, and saved me so much
time.
$ file alert.so1
alert.so1: SPARC demand paged shared library not stripped
The file was so old that to read the data I had to find an older
version of binutils that still supported a.out-sunos-big. Luckily
that worked and it was able to reconstruct a ton of useful data.
Here is the full recovered data, if you're interested.
More Luck
I got another stroke of luck, I found a third party printer driver on
an old SUNET archive for the Siemens Highprint 7400. Remarkably, it
had some ancient Codeview debugging data left in it.
Siemens Highprint 7400
Here is a picture of a lady printing some rorschach tests with her
new Highprint 7400, for some reason.
The debugging data was so old, I struggled to find a tool that could
parse it. The data had a version code NB02, which means they probably
used link.exe version 5, released around 1988. I tried TDUMP, CVDUMP
and even SYMDEB, and none recognized it. I did eventually find an old
enough version of Codeview that could read it, but 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 (Device Primitive) API worked.
Publics for: _DVTBL!*
Symbols for: L13PSI74!*
3CD5:016E struct psd
3CD5:006E struct GdvEntryTbl1_ pcf
3CD5:01DC unsigned int print_head_height_dots
3CD5:000A char grph_init_string[]
3CD5:0158 char gr_eol_str[]
3CD5:016C unsigned int col_res_num_pos
3CD5:0020 char start_grph_str[]
3CD5:0050 char last_grph_lf_str[]
3CB1:001C unsigned char far * graph_data_ptr
3CD5:0064 char gr_eog_str[]
3CD5:0006 unsigned long graph_data_size
3CD5:0000 unsigned int scan_width
3CD5:001E unsigned int stripNum
3AC8:0006 unsigned int PROC hmu_len()
[BP+0008] unsigned char far * lmbcs_str_buf
[BP+0006] unsigned int lbuf_len
[BP-0006] unsigned int return_hmu
[BP-000A] unsigned char far * bundle_ptr
[BP-0004] unsigned char far * orig_ptr
3AC8:0148 void PROC finish_down()
[BP+0006] unsigned int distance
3CB1:000C char max_feed[]
[BP-0006] int i
[BP-0004] unsigned char far * p
3AC8:01A4 int PROC graphics_init_printer()
Here is the full recovered data.
Hello World
IDA Reversing Lotus
With this data, and a weekend in IDA I was able to figure out a large
amount of the API. It was painful getting a toolchain that worked,
but I was eventually able to produce something that Lotus could load.
I think I must be the first person to write a Lotus 1-2-3 driver in
thirty years!
My driver didn't actually do anything, but when I responded to device
information requests claiming to support 256 columns, I saw in my log
that it sent me an LMBCS string to decode like this:
ENTER WriteLmbcsStringWithAttributes, caller 15f:6c7d
lotdemu.c:291: TRACE byteslen 238
lotdemu.c:293: TRACE attrs 000
lmbcs.c:104: TRACE translate_lmbcs
lmbcs.c:106: TRACE maxdst 256
lmbcs.c:107: TRACE maxsrc 238
000000: 20 20 20 20 41 20 20 20 20 20 20 20 20 42 20 20 A B
000010: 20 20 20 20 20 20 43 20 20 20 20 20 20 20 20 44 C D
000020: 20 20 20 20 20 20 20 20 45 20 20 20 20 20 20 20 E
000030: 20 46 20 20 20 20 20 20 20 20 47 20 20 20 20 20 F G
000040: 20 20 20 48 20 20 20 20 20 20 20 20 49 20 20 20 H I
000050: 20 20 20 20 20 4a 20 20 20 20 20 20 20 20 4b 20 J K
000060: 20 20 20 20 20 20 20 4c 20 20 20 20 20 20 20 20 L
000070: 4d 20 20 20 20 20 20 20 20 4e 20 20 20 20 20 20 M N
000080: 20 20 4f 20 20 20 20 20 20 20 20 50 20 20 20 20 O P
000090: 20 20 20 20 51 20 20 20 20 20 20 20 20 52 20 20 Q R
0000a0: 20 20 20 20 20 20 53 20 20 20 20 20 20 20 20 54 S T
0000b0: 20 20 20 20 20 20 20 20 55 20 20 20 20 20 20 20 U
0000c0: 20 56 20 20 20 20 20 20 20 20 57 20 20 20 20 20 V W
0000d0: 20 20 20 58 20 20 20 20 20 20 20 20 59 20 20 20 X Y
0000e0: 20 20 20 20 20 5a 20 20 20 20 20 20 20 20 Z
translated characters is 238
lotdemu.c:209: TRACE WriteStringToFramebuffer
It's asking me to write 26 column headers to the framebuffer!
This validated the idea, Lotus 1-2-3 does internally support
arbitrary resolutions!
Results
So.. many.. columns..
A ton of hacking later, and I do now have a usable driver for dosemu
that supports arbitrary resolutions, just look at all those columns!
I know enough about the API that I think I can probably add libcaca
support so that text mode graphs work, but that's a project for
another weekend!
Conclusion
My work-in-progress source code is available on github.
I've put all of my notes online so that in the event that anybody
else wants to make 123 DOS work somewhere new, they won't have to
repeat all my work!
HOME * ABOUT * CONTACT