Guide to character attributes in DOS text modes
DOS text modes are fairly straightforward. CGA had four text modes. Modes 0 and 1 are 40×25 modes, and 2 and 3 are 80×25 modes. MDA had a single mode – Mode 7 – which was also 80×25, but restricted to monochrome. Years later, VESA introduced five additional text modes which, to the best of my knowledge, no one ever used. Mode 108h is 80×60. Mode 109h through 10Ch all have 132 columns and 25, 43, 50, and 60 rows, respectively. Many graphics cards supported propriety text modes with 43 or more rows. Regardless of the column and row counts, all text modes work the same way.
Every character takes up two bytes in the frame buffer: one for the character to display, and one for the character's attributes. The short, official mapping for the character attribute byte is that it's split into two IRGB (Intensity, Red, Green, Blue) values, with the background in the high nibble and the foreground in the low nibble. All video cards can enable or disable blinking text. If blinking is enabled, Bit 7 represents blinking text. If blinking is disabled, it is treated as an intensity bit for the background. This means that that you can display 16 text (foreground) colors and either 8 background colors with blinking text, or 16 background colors without blinking text. Either way, each of the 256 possible character attribute values produces a different result (except for MDA, as we will see).
|* If blinking text is enabled, otherwise background intensity.|
For CGA, it's exactly this simple. A bit more explanation is necessary for other video standards. First, let's clarify what IRGB means.
MDA, CGA, and EGA all used a DE-9 connector, which is a connector with 9 pins and a small D-shaped shell. (CGA additionally supported composite video.) The data pins are digital: they are read as either "on" or "off".
CGA has pins for red, green, and blue chrominance (color), and one pin representing high intensity. The RGB pins represent 2/3 intensity, and the intensity pin adds 1/3 intensity to all three colors. This means that any color that is "on" goes from 2/3 to 3/3, and any color that is "off" goes from 0/3 to 1/3. The red, green, and blue pins, having two states each, allow for 8 colors, and the intensity pin creates a second set of high intensity colors, doubling the number to 16. CGA, therefore, uses a 4-bit IRGB system.
EGA has intensity pins for red, green, and blue, so each color can be 0/3, 1/3, 2/3, or 3/3 intensity, allowing 4 shades of each color, for 64 colors in total. (Although EGA monitors had circuity to display 64 colors, EGA modes could only display 16 colors simultaneously.) EGA uses a 6-bit RGBRGB system.
MDA works the same way, except that it's monochrome, so it only has a "video" and an "intensity" pin, which represent 2/3 and 1/3 luminance, respectively, but provide no chrominance information. (Light intensity, but not color.) Thus, it can display four shades of a single color. On a green phosphor monitor, that means shades of green. On an amber phosphor monitor, it means shades or orange. On a color monitor, it means shades of gray.
(VGA switched to a DE-15 connector with 6 extra pins, but actually stopped using separate pins for RGB and RGB intensity, while increasing color depth to a whopping 262,144 colors. This was accomplished by switching the red, green, and blue pins to analog and detecting 64 different values for each color.)
This is where I have to point out that the values in each nibble can't be assumed to be IRGB values on EGA or later graphics cards. EGA can display 64 different colors, but it can only display 16 of them at a time, so it has palette "slots" numbered 0 to 15. Likewise, VGA can display 262,144 different colors, but only 256 of them at a time, so it has palette "slots" numbered 0 to 255. In both cases, colors 0 to 15 are set to the 16 CGA colors by default. For EGA or higher, the values in each nibble should actually be considered palette numbers. Bit 7 continues to indicate blinking text if blinking is enabled, otherwise it enables palettes values of 8-15 for the background.
The system is so straightforward that, as we will see, emulators always get this right. How MDA's Mode 7 treats these values is less obvious, and many popular emulators get it wrong. We already know that MDA has one pin for video and one pin for intensity, allowing for four shades of the same color. Only four foreground and background RGB combinations have any meaning in MDA, and all others map to a safe default. In the manual for MDA and early manuals for the IBM PC Model 5150, IBM added the following information under the bitfield map from Figure 1:
"The blink and intensity bits may be combined with the foreground and background bits to further enhance the character attribute functions listed below."
R G B
R G B
|0 0 0||0 0 0||Non-Display|
|0 0 0||0 0 1||Underline|
|0 0 0||1 1 1||White Character/Black Background|
|1 1 1||0 0 0||Reverse Video|
By this definition, there are four attribute values that produce each function, based on the four possible combinations of the blink and intensity bits. Let's break this down.
Black foreground on black background produces "Non-Display", which corresponds with attribute values of 00h, 08h, 80h, and 88h. Neither blinking nor high intensity text have any effect.
Blue foreground on black background is rendered as underlined text, and is produced by values 01h, 09h, 81h, and 89h. Both blinking and high intensity are applied to the underline.
White foreground on black background is normal "White Character/Black Background", produced by values 07h, 0Fh, 87h, and 8Fh.
Black foreground on white background produces "Reverse Video", which corresponds with attribute values of 70h, 78h, F0h, and F8h.
What about the other 240 possible values? According to IBM, "The monochrome display adapter will produce white characters on a white background with any other code."
Testing on real hardware proves that the claim that only one foreground-background combination produces Underline, and that the 240 unlisted values produce white-on-white, are untrue. (In fact, there's no way to get white text on a white background under MDA.) So, why does IBM make these claims? A hint comes from a follow-up warning: "Code written with an underline attribute for the IBM Monochrome Display, when executed on a color/graphics monitor adapter, will result in a blue character where the underline attribute is encountered. Also, code written on a color/graphics monitor adapter with blue characters will be displayed as white characters on a black background, with a white underline on the IBM Monochrome Display."
It looks like IBM lied so that programers would only use values that would make programs look the same as they did on MDA hardware when run on CGA hardware: black and white (and, of necessity, blue for underlined characters). The truth is that text will be underlined when the foreground is set to blue, regardless of the background value. Also, all other foreground and background combinations display as white text on a black background, and not as white-on-white. IBM gave this away in an ASCII chart that lists the equivalent attribute values under MDA and CGA, where all blue foregrounds (hex values ending in 1 or 9) list as "Underline", and all other values that aren't Non-Display, Reverse Video, or Underline list as "Normal". (And all values from 80h to FFh additionally list as "Blinking".)
Giving an incomplete picture of how MDA handles 240 different attribute values was intended to prevent programmers using monochrome hardware from producing unpredictable results for CGA users, but it did a disservice to owners of CGA hardware who wanted to make games in something other than black and white that would also look good on MDA. As we shall see, it's perfectly acceptable to use background colors other than black in a game intended to support both CGA and MDA users. Here's a re-statement of Figure 1 that is specific to MDA:
|* If blink enabled, otherwise high intensity background for Reverse Video
** If foreground = 000. This means that Reverse Video and Underline are the only functions that can’t be combined.
All of this talk about the meaning of RGB values under MDA may lead to the impression that MDA is only black-and-white, until you remember the intensity bits. As with CGA and EGA, a value on the "Video" pin means 2/3 intensity, and the "Intensity" pin means 1/3 intensity. This means that normal text becomes 3/3 intensity when the foreground intensity bit is set. It also means that foreground text becomes 1/3 intensity when it's set to 0/3, but only under Reverse Video, not under Non-Display.
IBM doesn't mention what happens when blinking is disabled under MDA (or CGA). As with CGA, disabling blinking causes Bit 7 to be interpreted as high intensity background. Remembering that all background colors other than white are black under MDA, Bit 7=1 only changes Reverse Video from 2/3 to 3/3; it does not change a 0/3 background to 1/3.
Unlike the color text modes, where both foreground and background can be set to any color (16×16=256 combinations), only the following combinations are possible under MDA:
|1/3||2/3||Reverse video, high intensity|
|0/3||3/3||Reverse video, blink set but disabled|
|1/3||3/3||Reverse video, high intensity, blink set but disabled|
"Non-Display" can be regarded as 0/3 on 0/3. That means that only 6 of the 12 combinations where the foreground and background colors would be different, and only 7 of the 16 total combinations are possible.
Evaluating character attribute handling under PCem and DOSBox
For most people, the default way to play DOS games on modern computers is through DOSBox, and it's easy to see why. DOSBox emulates not only the hardware of a DOS-era PC, but also the DOS commands and API. Other emulators require you to install a copy of DOS on a virtual hard drive, whereas with DOSBox you only have to map a folder on your hard drive to an available drive letter in DOSBox, and you can start playing games right away. Emulation is accurate for the majority of games, so what more could you want?
DOSBox also offers advanced features that are helpful to me, such as the ability to record videos in a lossless compression format designed specifically for DOS games called ZMVB (Zipped Motion Video Block). To create screenshots for the website, I record myself playing the game and then search for the perfect frame after I think I've gotten the shot that I wanted. If I tried to take a screenshot while playing the game, I would have to stop playing the game to press the button(s) to take a screenshot, and I would be unlikely to get the exact frame that I wanted.
DOSBox also has a debugger that lets me keep track of what's going on in the emulated hardware, and I can set break points for a particular interrupt, or place a watch on a specific address in memory. I often set a watch for changes to [40:49] or break on INT 10h AH=0 to be alerted when the video mode changes.
For all of these reasons, I have always used DOSBox to review games and take screenshots for the site. Originally, there were no screenshots on the site. When I decided to have screenshots for every video mode supported by every game, I had to go back and take hundreds of screenshots, and I prioritized games that had graphics. When I got around to the text-based games, I started to notice that the screenshots were inaccurate. To resolve that problem, leilei recommended that I use PCem. PCem is an incredibly accurate emulator, in part because it requires the use of the actual ROMs that were present on the hardware that it can emulate. The only issues that prevent me from using it as my primary emulator for the purposes of this website are the lack of support for ZMVB (or some other lossless video format) recording, and the lack of a full-featured debugger (as far as I know).
It's probably easier (and beneficial to a larger number of users) to fix DOSBox rather than add features to PCem. To figure out what's wrong with DOSBox, I have created a suite of utilities that allow me to change or check the video mode, set the border color, enable or disable text blinking, and display all 256 character attributes.
The screenshots below show each of the 256 characters from Code Page 437, with the character attribute set to the same value. When blinking text is enabled, I took the screenshot while the text was being displayed. Be advised that all characters from 0x80 through 0xFF are blinking when blink is enabled. I used PCem v16 and DOSBox 0.74-3. Based on feedback from the DOSBox crew, I then re-tested using a vanilla SVN build of DOSBox to see what will be fixed in DOSBox 0.75.
On the left, PCem matches real hardware for both MDA and Hercules, with the following exceptions:
- The background should be 2/3 for characters 70h, 78h, F0h, and F8h while blink is enabled. Instead, the background is 3/3 regardless of whether or not blink is enabled.
- The foreground should be 1/3 when Reverse Video is combined with High Intensity foreground (78h, F8h) instead of 2/3.
DOSBox doesn't have (or need) an MDA machine because Hercules is identical to MDA while in text mode. On the right, we see significant differences between DOSBox and real hardware:
- DOSBox falls for the IBM PC manual's assertion that there are only four values that produce an underline (blue foreground on black background).
- The foreground should be 1/3 when Reverse Video is combined with High Intensity foreground (78h, F8h) instead of 3/3.
- The background should be 3/3 for characters 70h, 78h, F0h, and F8h when blink is disabled. Instead, the background is 2/3 regardless of whether or not blink is enabled.
- DOSBox creates 8×14 characters, failing to add the derived 9th pixel, producing an effective resolution of 640×350 instead of 720×350.
It should be fairly easy to fix #1: always interpret blue foreground as underline. Why fix this? So that games that were designed to run on both CGA and MDA hardware will display correctly. Yes, I have an example.
Infocom made a great many text adventures that run in an interpreter, and the interpreter for DOS renders all games using white text on a blue background, except for a status bar at the top of the screen which uses blue text on a white background. This color scheme was a deliberate decision to support all PC users. It looked better than mere black and white for those who had color graphics cards and monitors, and still looked good for users with MDA cards and monochrome monitors. Below, we see Zork I as you are probably used to see it.
Here's how it looks under MDA/Hercules under PCem and DOSBox.
The white text on blue background is correctly rendered as white text on a black background in both emulators (and not white-on-white as IBM had warned), and the blue text on white background is correctly rendered as underlined text on PCem, creating a dividing line between the status bar and the game text. DOSBox only displays an underline for blue text on a black background, and so all Infocom games render incorrectly on Hercules in DOSBox, making the status bar blend in with the game text.
Perhaps because IBM didn't explain what should happen in their manuals, both emulators fail to interpret Bit 7 as background intensity when blinking is disabled, and both incorrectly handle the foreground color on characters 78h and F8h. In summary:
|Reverse video, blink enabled||#000000||#AAAAAA||#000000||#FFFDED||#000000||#AAAAAA|
|Reverse video, blink disabled||#000000||#FFFFFF||#000000||#FFFDED||#000000||#AAAAAA|
|Reverse video, high intensity, blink enabled||#555555||#AAAAAA||#AFB3B0||#FFFDED||#FFFFFF||#AAAAAA|
|Reverse video, high intensity, blink disabled||#555555||#FFFFFF||#AFB3B0||#FFFDED||#FFFFFF||#AAAAAA|
On the left, PCem pads the top, bottom, left, and right sides of the screen with 8 extra pixels in order to emulate the border, which creates a 656×416 screenshot. There are games that use the border to convey status information, so this is a really nice feature. I have set the border to 13 (bright magenta) to make it easier to see where the overscan area ends and the normal area begins. In all four text modes, with blink enabled or disabled, the only difference is character 0x0F.
Based on what we know about the red, green, and blue pins representing 2/3 intensity, and the intensity pin representing 1/3 intensity, color #6 (low intensity Red+Green) should be a dark yellow (#AAAA00). IBM thought that having a brown was more important than an ugly dark yellow, so CGA monitors had wiring to reduce the green intensity of that color combination to 1/3, producing #AA5500. Both emulators correctly produce brown instead of dark yellow.
EGA can display up to 16 simultaneous colors from a palette of 64. By default, the palette is set to match the 16 CGA colors. The low intensity CGA colors are the first 8 colors in the EGA palette (0-7), and the high intensity CGA colors are the last 8 (56-63), with one exception. EGA #6 is the low intensity yellow (#AAAA00) that CGA logically should have used as #6, instead of the brown (#AA5500) that it actually displayed. CGA #6 is EGA #20, and this is set in EGA's palette #6 by default on real hardware and PCem. DOSBox incorrectly sets palette #6 to EGA #6. I assumed that I would find some bit of initialization code in the source code with a loop that set palette 0-7 to 0-7, and 8-15 to 56-63, and I would just add an extra condition to set the color to #20 if palette # == 6. I didn't find any obvious code that sets the palette colors, so I have been unable to make this change myself.
The only other issue with DOSBox's EGA machine is that there is no way to disable blinking text and enable high intensity background colors. The meaning of character attribute Bit 7 cannot be changed with either the MDA/CGA method of setting Bit 5 of the Mode Control Register, nor the correct EGA method of using INT 10h AX=1003h.
Note: Both of these issues have been fixed in the upcoming 0.75 release of DOSBox.
For some reason, PCem produces an image that is 640×349, cutting off the bottom row.
Contrasting EGA, DOSBox 0.74-3's "vgaonly" machine handles all CGA text modes correctly. It uses brown instead of dark yellow for color #6, it enables or disables blinking through INT 10h AX=1003h, and it adds the derived 9th pixel to create 9×16 characters, producing the correct effective resolution of 720×400.