One of the things which made the floppy disk version of Quest for Glory: Shadows of Darkness (QFG4) unique from its CD successor is that it supported the original Logitech CyberMan 3D Controller. The CyberMan was an interesting peripheral which acts like a mouse which can (somewhat) move in 3D space (X-Y-Z coordinates in addition to pitch, yaw, and roll). It was only supported on a handful of games from the mid-90s, such as DOOM, Hexen, and Quest for Glory: Shadows of Darkness. If batteries are added, the CyberMan also provides tactile feedback. This was an early attempt of creating a 6-axis controller, but it wouldn't be effectively implemented until the Playstation 3's SIXAXIS controller.
The following products supported the CyberMan:
- Quest for Glory: Shadows of Darkness
- Body Adventure
- Lands of Lore
- Spectre VR
- System Shock
- Terminator Rampage
- Lemmings 3D
- Ravenloft: Strahd's Possession
- Ravenloft: Stone Prophet
- Lords of Midnight
- Mechwarrior 2
- Rise of the Triad
- Under a Killing Moon
This list is not exhaustive, and as do more research, I discover additional games which supported (or claim to have supported) the CyberMan. The two Ravenloft games and Menzoberranzan are based off of the same game engine, which explains why the CyberMan works with all three. Only the floppy disk version of Quest for Glory 4 worked with the CyberMan, as the CD release explicitly mentions that it does not support this controller.
The CyberMan I purchased off eBay came in near pristine condition. The box was in excellent condition, in addition to the manuals, floppy disks, and the controller. The only true evidence that this CyberMan had been used is because batteries had been left inside...and after an inditerminate amount of time, the batteries had leaked. Sigh. Fortunately, after I got everything set up and working, I was able to confirm that the tactile feedback does still work when I tested it in Quest for Glory: Shadows of Darkness.
USB to Serial
The first order of business was to ensure that the device actually worked. The first iteration of the CyberMan was released in 1993, several years before USB became commonplace for computer peripherals. Instead, the CyberMan uses an RS-232 9-pin serial connector, which is an obstacle in trying to interact with modern computers which generally don't have serial ports these days.
Since I initially started the testing on a Mac, I purchased the following cable from Micro Center: QVS USB 2.0 (Type-A) Male to DB-9 RS-232 Serial Male Adapter Cable 6 ft. - Black. To get more details about the cable, I used the Mac's System Profiler which identified the cable as:
Product ID: 0x2303
Vendor ID: 0x067b (Prolific Technology, Inc.)
Speed: Up to 12 Mb/sec
Manufacturer: Prolific Technology Inc.
Location ID: 0x14100000 / 28
Current Available (mA): 500
Current Required (mA): 100
Extra Operating Current (mA): 0
One note about the supplied power is what USB and traditional serial ports could offer, which generally was enough to power a standard mouse or joystick, but to enable the tactile vibration, batteries were needed to give the extra power.
For some delightful technical goodness, the command line gives a few extra details, such as that the Prolific PL2303 driver is used for this adapter.
% ls /dev/tty.usb*
% kextstat | grep prolific
188 0 0xffffff7f83ae3000 0x7000 0x7000 com.prolific.driver.PL2303 (1.6.3)
E55010C7-C7DD-36D8-AE20-E050E3AFCB2B <61 52 6 5 3>
% ioreg -c IOSerialBSDClient | grep usb
| | "IOTTYBaseName" = "usbserial"
| | "IOCalloutDevice" = "/dev/cu.usbserial"
| | "IODialinDevice" = "/dev/tty.usbserial"
| | "IOTTYDevice" = "usbserial"
Another graphical utility to get additional information about the connected adapter is to use the development tool IORegistryExplorer. This app used to be provided with Xcode by default, but now it is a separate download as part of the Additional Tools for Xcode package which is downloaded from Apple's developer portal.
It had been about ten years since I had worked with a USB to serial converter, but I did find some old notes which reminded me I had to look for a PL2303 driver to get the cable to work properly with macOS. Unfortunately, the old driver I had used previously was no longer freely available via its SourceForge page. Fortunately, I found that the manufacturer of my cable did offer an up-to-date driver. Downloading and installing the appropriate driver worked perfectly under macOS Mojave. Note: Extra security measures in more modern versions of macOS (Big Sur and later) seem to have issues with some kernel extensions, so I have not tried this adapter with macOS Big Sur or Monterey, yet.
With the PL2303 driver now installed, I next turned to using my favorite serial program Parley from Buttered Cat Software. After setting some options (4800 7-N-1) and connecting the CyberMan (seen as a
usbserial device to Parley), I was able to see some output from the device to confirm that it did work.
From Parley (4800 baudrate with 7 data bits : 4800 7-N-1)
**** Port Open
ø øø øx ø xx ø x ø x ø x ø x ø x ø xøøøø xøøøø x ø x ø x ø x ø x ø x ø xøøxøx x ø x ø x ø
øø ø x x ø øø xx øxøxø x xø ø ø ø x x x
x x x x xø x x x øø x ø x øø x ø x øø x x x x x x x x x x
x x x x x x x x x x x x xøxø xø øxxø øxø xø øø ø x ø x øø
ø ø øø x øø x x ø x øøø øøø x øø
From what I've read, most serial mice used a 1200 baud, 7 bit setting (1200-7-N-1), which is unfortunate since the lowest baud setting in Parley is 4800. Another option was to use command line tools.
screen /dev/cu.usbserial 1200
Another option I tried was to communicate with a serial device in DOS in either DOSBox or VirtualBox. Unfortunately, I was never able to get any of these emulated versions of DOS running on my Mac (yet) to see the serial device, but here are some different options I tried.
set port 1
set baud 9600
None of the data from Parley or
screen was very intelligible, but it did prove that the device worked to some degree. I switched over to CoolTerm, which does have the option to set the baudrate all the way down to 300 baud. But for the ideal configuration, I set the options for this connection to 1200 baud rate at 7 data bits and 1 stop bit with no parity (1200 7-N-1). The data from the CyberMan now looked like this:
Ä√ΩÄ√∑Ä√®Ä√®Äœ®Ω√®Ä√®Ä√®Ä√®Ä√®Ä¡ÇÄ¡ùÄ¡∏Ä¡ãÄ¡∏Ä¡∏Ä¿ïÄ¿πÄ¿ÉÄ√∑Ä¬†Ä¬àÄ¬¶Ä à†¬∏Ä óàÃ
CoolTerm has an option to convert the garbled ASCII output into hex codes, which is far more manageable to dissect the data coming from various commands sent by the CyberMan. All further data mentioned in this article will be either hex values (
C0) or binary (
Serial Mouse Protocol
Before delving too deeply into CyberMan's technical details, let's inspect how the Microsoft serial mouse protocol works, which is the basis for how Logitech mice also worked. These details are from the original archived article by Tomi Engdahl <firstname.lastname@example.org>, which is the basis for numerous other documents found on the internet.
D7 D6 D5 D4 D3 D2 D1 D0
Byte 1 X 1 LB RB Y7 Y6 X7 X6
Byte 2 X 0 X5 X4 X3 X2 X1 X0
Byte 3 X 0 Y5 Y4 Y3 Y2 Y1 Y0
LB is the state of the left button (1 means pressed down)
RB is the state of the right button (1 means pressed down)
X7-X0 movement in X direction since last packet (signed byte)
Y7-Y0 movement in Y direction since last packet (signed byte)
1st byte 2nd byte 3rd byte
================ =============== ================
- 1 ? ? Y Y X X - 0 X X X X X X - 0 Y Y Y Y Y Y
================ =============== ================
| | \ / \ / \---------/ \---------/
| | | | | |
| | | \----\ | |
| | \--------|-------|--------\ |
| | / \ /---------\ / \ /---------\
| | ================ =================
| | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Left Button --/ | ================ =================
Right Button ----/ X increment Y increment
Each time the mouse state changes (e.g. the mouse moves or buttons are pressed/released), a data packet is sent to the host. Each data packet is comprised of three 7-bit bytes. In the data culled from the CyberMan, each byte is made up of 8 bits, but the leading bit is always set to 1, but it is ignored, so an "empty" byte will still be
1000 0000 in binary). An example data packet of the left mouse button being pressed would be
E0 80 80 .
Note: The bit marked with X is 0 if the mouse received with 7 databits and 2 stop bits format. It is also possible to use 8 databits and 1 stop bit format for receiving. In this case, X gets the value 1. The safest thing to get everything working is to use 7 databits and 1 stopbit when receiving mouse information (and if you are making a mouse then send out 7 databits and 2 stop bits).
The byte marked with 1 is sent first, then the others. The bit D6 in the first byte is used for synchronizing the software to mouse packets if it goes out of sync.
According to this description of the PC mouse by Petr Simandl,
D6 of Byte 1 will always be 1. This corresponds to the CyberMan output where the first byte in the data packet is at least
1100 0000). Having the
D6 bit set to 1 indicates the start of a new data packet, so any subsequent byte in the packet will not have the
D6 bit set.
Examples of the first byte of a data packet, dependent upon various button states:
No buttons down: 0XC0 = 1100 0000
Left button down: 0xE0 = 1110 0000
Right button down: 0xD0 = 1101 0000
From the Serial Mouse Detection documentation:
The following is the data report format. Data is transmitted serially (LSB first) in the form of seven bit bytes. X and Y data are incremental movements with Y movement considered positive to the south. (For the PS/2 data format, Y movement was considered positive to the north). The data packet format is three or four bytes long. The fourth byte is transmitted in either of the following cases
a. The middle button is depressed
b. A report is sent while the middle button is depressed
c. The middle button is released
Additional details from Logitech Logimouse C7 Firmware Rev 3.0 Jan86 corroborates the original details on the serial mouse data format:
12.2 Microsoft. Compatible Data Format
In the Microsoft Compatible Format, data is transferred in
the form of seven bit bytes. Each report consists of three
bytes. X and Y are relative movements. In the Microsoft
Compatible Format, Y movement is positive to the south and
negative to the north.
The command to select the Microsoft Compatible Format is 'V'
6 5 4 3 2 1 0 Bit number
1 L R Y7 Y6 X7 X6 Byte 1
0 X5 X4 X3 X2 XI X0 Byte 2
0 Y5 Y4 Y3 Y2 Yl Y0 Byte 3
L,R = Key data (Left, Right key) 1 = key depressed
X0-X7 = X distance 8 bit two's complement value -128 to +127
Y0-Y7 = Y distance 8 bit two's complement value -128 to +127
Positive = South
If LOGIMOUSE C7 is set by jumpers to the Microsoft
Compatible Format, at power-up it will send one character
'M' (4DH) . No character is sent if the Microsoft Compatible
Format is selected with a command.
LOGIMOUSE C7-M always sends 'M' (4DH) when the host toggles
the RTS signal line. In response to a Microsoft driver reset
(i.e. toggling RTS), LOGIMOUSE C7-M sets up its operating
parameters to Microsoft compatible format, Incremental
Stream, continuous reports, 1200 baud, regardless of the
current settings or the jumpers.
3 Button Logitech Protocol Extension
These three different
pages detail how Logitech extended the Microsoft mouse protocol to support a third mouse button.
Logitech uses this same protocol in their mice (for example Logitech Pilot mouse and others). The original protocol supports only two buttons, but Logitech has added a third button to some of their mouse models. To make this possible Logitech has made one extension to the protocol.
Logitech extended the 2 button mouse protocol to support 3 button mice by adding a 4th byte when the middle button is pressed (and the first packet after it is released). If a 4th byte is encountered (i.e., an
extra byte with D6 set to 0) then D5 of that byte (0x20 or 32 in decimal) indicates the status of the middle mouse button.
Logitech serial 3-button mice use a different extension of the Microsoft protocol: when the middle button is up, the above 3-byte packet is sent. When the middle button is down a 4-byte packet is sent, where the 4th byte has value 0x20 (or at least has the 0x20 bit set). In particular, a press of the middle button is reported as 0,0,0,0x20 when no other buttons are down.
I have not seen any documentation about the exact documents, but here is what I have found out: The information of the third button state is sent using one extra byte which is send after the normal packet when needed. Value 32 (dec) is sent every time when the center button is pressed down. It is also sent every time with the data packet when center button is kept down and the mouse data packet is sent for other reasons. When the center button is released, the mouse sends the normal data packet followed by data byte which has value 0 (dec).
This looks very much in line with what is seen in the section below when pressing the middle button sends four bytes (e.g.
C0 80 80 A0), whereas any other action sends three bytes. This is an interesting method to implement the middle button by adding an additional byte. According to the documentation above, the D5 bit is set, so the byte will have the value of
0010 0000). Since each of these bytes in the data packet have the first bit always set to 1, the returned value is
1010 0000, which is
0x80 + 0x20). The computer determines when a new data packet comes in by checking if the D6 bit is set to 1. Other mouse protocols ended up using more bytes per data packet to deliver additional data.
CyberMan Hex Codes
The CyberMan's controls are more akin to a joystick than a typical mouse by supporting the movement of the controller for yaw, roll, pitch, and also along the Z-axis. The following are the responses from the CyberMan when each of its controls are activated.
Left button down: E0 80 80
Left button up: C0 80 80
Middle button down: C0 80 80 A0
Middle button up: C0 80 80 80
Right button down: D0 80 80
Right button up: C0 80 80
L+R buttons down: F0 80 80
L+M buttons down: E0 80 80 A0
M+R buttons down: D0 80 80 A0
L+M+R buttons down: F0 80 80 A0
Left: C3 A8 80
Right: C0 98 80
Forward: CC 80 A8
Backwards: C0 80 98
Forward-Left: CF A8 A8
Forward-Right: CC 98 A8
Backwards-Left: C3 A8 98
Backwards-Right: C0 98 98
Up: CC 80 BD
Down: C3 BD 80
Rotating-Left: C0 86 80
Rotating-Right: C3 BD 80 (Note: Same value as Down, one of these are likely incorrect)
Right: C3 BD 80
Left: C0 8F 80
Forward: C0 80 8F
Back: CC 80 BD
These are the approximate values I could get from the output. The data for the three buttons and moving along the X-Y plane are consistent, but the other data seems far more chaotic.
The first byte of each data packet is at least
1100 0000), indicated by the D6 (second from left) bit being set. Most of the data packets are three bytes, unless the middle button is pressed, which then increases each packet to four bytes.
C0 = 1100 0000 : No buttons down
E0 = 1110 0000 : Left mouse button down
D0 = 1101 0000 : Right mouse button down
F0 = 1111 0000 : Both left and right buttons down
As shown in the Packet Format diagram, if the
D5 bit (third from the left) is set in the first byte, it indicates that the left button is pressed. If the
D4 bit (fourth from the left) is set, then the right mouse button is pressed. If the first byte is
F0, then it indicates that both the left and right buttons are pressed, so the
D4 bits are set.
As mentioned in the 3 Button Logitech Protocol Extension section, the middle button is indicated by the addition of a fourth byte. When the middle button is pressed, the
A0 is the extra byte. When the middle button is released, an extra
80 is available to indicate that the middle button is no longer active.
Middle button down: C0 80 80 A0
Middle button up: C0 80 80 80
This fourth byte will only appear when the middle button is active or the middle button has just been released. This adds a little extra complexity to the mouse driver, because it cannot always assume that each data packet is going to be exactly three bytes in length. Instead, the driver needs to check if the
D6 bit on a byte is set to determine the start of a new data packet. Notice that the fourth byte is given a value of
A0, which is
1010 0000 in binary, and the
D6 bit is 0. When the middle button is released, the fourth byte returns to an "empty" state of
Calculating the X and Y positions is interesting by the way it takes two bits from the first byte, and then combines it with the last six bits of either the third or fourth bits.
Left: C3 A8 80 (11000011 10101000 10000000)
Right: C0 98 80 (11000000 10011000 10000000)
Forward: CC 80 A8 (11001100 10000000 10101000)
Backwards: C0 80 98 (11000000 10000000 10011000)
The X (left and right) coordinates take the last two bits (
D0) from the first byte and combine it with the last six bits from the second byte. For the Y coordinates, it takes the third and fourth (
D2) bits from the first byte and then merges those with the last six bits of the third byte.
Left: 11101000 => E8
Right: 00011000 => 18
Forward: 11101000 => E8
Backwards: 00011000 => 18
It's interesting to see when the max values of each direction are constructed, that both Left and Forward equal
E8, and Right and Backwards equal
Now that we have covered the basic operations, let's combine them and inspect the output. Moving the CyberMan Forward and Left will return
CF A8 A8, and holding down the left mouse button at the same time will return
EF A8 A8.
Forward-Left: CF A8 A8 (CC 80 A8 | C3 A8 80 => CF A8 A8)
Forward-Right: CC 98 A8 (CC 80 A8 | C0 98 80 => CC 98 A8)
Backwards-Left: C3 A8 98 (C0 80 98 | C3 A8 80 => C3 A8 98)
Backwards-Right: C0 98 98 (C0 80 98 | C0 98 80 => C0 98 98)
There is a computational beauty of how the various input values are calculated, which uses the bitwise OR operator
(indicated by the vertical pipe character:
|). If both the left and right mouse buttons are down at the same time, then the resulting data packet is
F0 80 80, which is calculated by
E0 | D0 => F0.
1110 0000 (E0)
OR 1101 0000 (D0)
1111 0000 (F0)
If the mouse is set to Backwards-Left and the middle button is pressed down, the data packet
C3 A8 98 A0 is sent. Once the middle button is released (but the mouse isn't moved from its position), the data packet
C3 A8 98 80 is sent once and then the standard three byte data packet
C3 A8 98 is afterwards.
It appears that the X-Y values for the 2nd and 3rd bytes have a range between
1010 1000) and
1001 1000), which leaves room for the CyberMan to handle the extra functionality for the Z-axis, yaw, pitch, and roll. The values I saw in the terminal were not as consistent, but it appears that any extra values were either higher (
BD) or lower (
This covers traditional mouse functionality, but what set the CyberMan apart from its traditional counterparts is its ability to work in three dimensions. Additional details per the Logitech CyberMan's manual:
- Using Z: Pull up and push down lightly on CyberMan to use the Z movement. In a game, you might use the Z movement to jump up or crouch down.
- Using Yaw: Twist CyberMan slightly to the left or right to use the Yaw movement. Looking right or left are typical Yaw actions in 3D games.
- Using Roll: Tilt CyberMan gently to the right or left to use the Roll movement. In some games, use Roll to move left or right.
- Using Pitch: Incline CyberMan slightly forward or slightly backward to use the Pitch movement. In your 3D games, pitching simulates actions like looking up or down. Some games use Pitch to walk forward.
Looking at how the Z-axis is handled, it initially looks like it is moving Forward (
CC) when going Up, but the third byte is
BD, which is larger than a normal Forward value can achieve. There is a similar approach when pressing the mouse Down. The first byte is
C3, which looks like the mouse is moving left, but the second byte is
BD, which indicates it is not within the standard range of moving along the X-axis. When releasing from the Up position, a data packet of
C0 80 83 is sent.
Up: CC 80 BD
Down: C3 BD 80
The Yaw, Roll, and Pitch follow similar approaches of communication by using a mixture of values between the three bytes to indicate what type of data is being sent. Yaw and Roll make use of the second byte, whereas Pitch uses the third byte. The data I'm seeing is inconsistent, so take some of these examples with a grain of salt, and this will require further testing to get more reliable results, or my CyberMan may not be 100% functional.
The Logitech CyberMan 3D Controller was an interesting PC peripheral that was a little ahead of its time. It was followed up by the Logitech CyberMan 2 a couple years later, which sported a different form factor, likely to address the ergonomic difficulties the original possessed, but the second model didn't set the world on fire, either.
This has been an interesting experiment to delve into the ancient world of serial mice and odd peripherals, one which I intend on further pursuing to see if I can get the CyberMan to successfully work on modern computers and try out some of the supported games.