Ultima I Full Screen Graphic Format
|Hardware||CGA, EGA, Tandy|
|Colour depth||2-bit (CGA), 4-bit (EGA, Tandy)|
|Minimum size (pixels)||320×200|
|Maximum size (pixels)||320×200|
|Palette||Default CGA, External EGA/Tandy|
This format is used by Ultima I to store full-screen (320×200) images, including the title screen and ending screen. Technically speaking, there are three different encodings for this format, but they're grouped here for brevity. Castle.4 is used for CGA displays, castle.16 is used for EGA and Tandy displays, and nif.bin is used on all three displays.
The format has no header. The size of the image and the color palettes are determined by the EXE, so modification is limited. Both formats are uncompressed.
|BYTE||pixels||Binary representation of 2, 4, or 8 pixels.|
Castle.4 uses 2-bit color, so it holds 4 pixels per byte. Bits 7 and 6 are the color of the first pixel, 5 and 4 are the color of second pixel, and so on. The graphic data is stored left-to-right, top-to-bottom, but the lines are interlaced; first the even lines from 0-198 are stored, then the odd lines from lines 1-199. There are 192 bytes of data of padding between the even and odd lines to accommodate the memory structure of the CGA standard.
EGA / Tandy
Castle.16 uses 4-bit color, so it holds 2 pixels per byte. The high nibble is the color of the first pixel, the low nibble is the color of the second pixel. The EGA data is stored as linear EGA data from left-to-right, top-to-bottom.
|EGA Default||EGA Palette||Tandy Palette|
|0 - Black||0 - Black||0 - Black|
|1 - Blue||8 - Dark Gray||8 - Dark Gray|
|2 - Green||7 - Gray||7 - Gray|
|3 - Cyan||15 - White||15 - White|
|4 - Red||15 - White||15 - White|
|5 - Magenta||2 - Green||2 - Green|
|6 - Brown||10 - Lt. Green||10 - Lt. Green|
|7 - Gray||6 - Brown||15 - Yellow|
|8 - Dark Gray||12 - Lt. Red||4 - Red|
|9 - Lt. Blue||7 - Gray||7 - Gray|
|10 - Lt. Green||13 - Lt. Magenta||13 - Lt. Magenta|
|11 - Lt. Cyan||11 - Lt. Cyan||11 - Lt. Cyan|
|12 - Lt. Red||1 - Blue||1 - Blue|
|13 - Lt. Magenta||9 - Lt. Blue||9 - Lt. Blue|
|14 - Yellow||7 - Gray||7 - Gray|
|15 - White||15 - White||15 - White|
Although the game uses the standard EGA Palette for EGA and Tandy modes, the title screen graphic uses a custom cross-over replacement palette which differs from the EGA standard, and each other. The artist seems to have used the CGA title screen graphic's pixel layout, but, rather than recolor the bands of cyan on the castle's turrets, the developers just reworked which colors were drawn. Unfortunately, this means that you can't swap the EGA/Tandy title screen graphic with an existing EGA graphic. Any graphic you use as a replacement must conform to the cross-over colors. Alternately, you can hack the ultima.exe file and replace the cross-over table, though the location in the EXE is currently unknown.
The EGA palette is missing cyan, red, magenta, and yellow. The Tandy palette is missing cyan, lt. red, and brown. This strange color crossover is only used at the title screen. For the rest of the game, the default EGA color palette is used for EGA and Tandy modes.
This is how the EGA/Tandy title screen graphic would be displayed on the screen if the cross-over palette was not used:
Nif.bin uses 1-bit color, so it stores 8 pixels per byte. Because the text in the ending screen doesn't quite fill the screen, the file only stores 168 rows instead of all 200. The end game also prints "Press CONTROL-ALT-DEL to Restart" on the bottom of the screen. This text is printed from mondain.exe and is not part of the image. The color is cyan for CGA displays, and lt. cyan for EGA and Tandy.
The following FreeBASIC code will load and display the CGA, EGA, and Tandy title screens.
' Draws the title and ending screens of Ultima 1 for DOS ' in CGA, EGA, and Tandy modes. Dim As UByte X Dim As UByte Y Dim As UByte Pixels ' === Title Screen CGA === Screen 1 Color 0, 1 ' Low intensity cyan/magenta/white palette. Open "H:\DOS\Ultima\Castle.4" For Binary As #1 Dim As UByte ColorBlock Dim As UByte Pixel Dim As UByte Offset Y = 0 Do For X = 0 To 79 Get #1, , Pixels ColorBlock = 7 For Offset = 0 To 3 If Bit(Pixels, ColorBlock) = -1 Then If Bit(Pixels, ColorBlock - 1) = -1 Then Pixel = 3 Else Pixel = 2 End If Else If Bit(Pixels, ColorBlock - 1) = -1 Then Pixel = 1 Else Pixel = 0 End If End If PSet(Offset + (X * 4), Y), Pixel ColorBlock = ColorBlock - 2 Next Offset Next X ' The CGA image is interlaced, so we skip a line as we read. When we ' reach the bottom of the screen, we will wrap back up to the top. Y = Y + 2 If Y = 200 Then ' When we reach the end of the first pass, there are 192 bytes ' (768 pixels worth) of data that isn't displayed. This code reads ' the data and throws it away before we jumping up to line 1. For Y = 0 To 191 Get #1, , Pixels Next Y Y = 1 End If Loop Until Y = 201 Close #1 Sleep ' === Title Screen EGA === Screen 7 Open "H:\DOS\Ultima\Castle.16" For Binary As #1 Dim As UByte HiNibble Dim As UByte LoNibble ' The artist used bands of color to give the castle better depth in the CGA ' art. The developers used the same art as the CGA image for the EGA ' display, but the color bands didn't look right in EGA. Rather than fix ' the art, the developers just reassigned the palette so the color bands are ' all the same color. This is why gray and white are used multiple times. Dim As UByte EGAPalette(0 To 15) EGAPalette(0) = 0 EGAPalette(1) = 8 EGAPalette(2) = 7 EGAPalette(3) = 15 EGAPalette(4) = 15 EGAPalette(5) = 2 EGAPalette(6) = 10 EGAPalette(7) = 6 EGAPalette(8) = 12 EGAPalette(9) = 7 EGAPalette(10) = 13 EGAPalette(11) = 11 EGAPalette(12) = 1 EGAPalette(13) = 9 EGAPalette(14) = 7 EGAPalette(15) = 15 For Y = 0 To 199 For X = 0 To 159 Get #1, , Pixels ' The EGA file stores 2 pixels per byte. The hi nibble is the first ' color, the lo nibble is the second. HiNibble = Pixels SHR 4 HiNibble = HiNibble And &h0F LoNibble = Pixels AND &h0F PSet(X * 2, Y), EGAPalette(HiNibble) PSet(X * 2 + 1, Y), EGAPalette(LoNibble) Next X Next Y Close #1 Sleep ' === Title Screen Tandy 1000 === Screen 7 Open "H:\DOS\Ultima\Castle.16" For Binary As #1 Dim As UByte TandyPalette(0 To 15) TandyPalette(0) = 0 TandyPalette(1) = 8 TandyPalette(2) = 7 TandyPalette(3) = 15 TandyPalette(4) = 15 TandyPalette(5) = 2 TandyPalette(6) = 10 TandyPalette(7) = 14 TandyPalette(8) = 4 TandyPalette(9) = 7 TandyPalette(10) = 13 TandyPalette(11) = 11 TandyPalette(12) = 1 TandyPalette(13) = 9 TandyPalette(14) = 7 TandyPalette(15) = 15 For Y = 0 To 199 For X = 0 To 159 Get #1, , Pixels ' The EGA file stores 2 pixels per byte. The hi nibble is the first ' color, the lo nibble is the second. HiNibble = Pixels SHR 4 HiNibble = HiNibble And &h0F LoNibble = Pixels AND &h0F PSet(X * 2, Y), EGAPalette(HiNibble) PSet(X * 2 + 1, Y), EGAPalette(LoNibble) Next X Next Y Close #1 Sleep ' === Ending Screen CGA === Screen 1 Color 0, 1 ' Low intensity cyan/magenta/white palette. Open "H:\DOS\Ultima\nif.bin" For Binary As #1 For Y = 0 To 167 For X = 0 To 39 Get #1, , Pixels Offset = 7 For ColorBlock = 0 To 7 If Bit(Pixels, Offset) = -1 Then Pixel = 3 Else Pixel = 0 End If PSet((X * 8) + ColorBlock, Y), Pixel Offset = Offset - 1 Next ColorBlock Next X Next Y Close #1 Sleep ' === Ending Screen EGA / Tandy 1000 === Screen 7 Open "H:\DOS\Ultima\nif.bin" For Binary As #1 For Y = 0 To 167 For X = 0 To 39 Get #1, , Pixels Offset = 7 For ColorBlock = 0 To 7 If Bit(Pixels, Offset) = -1 Then Pixel = 15 Else Pixel = 0 End If PSet((X * 8) + ColorBlock, Y), Pixel Offset = Offset - 1 Next ColorBlock Next X Next Y Close #1 Sleep
This graphic format was reverse engineered by TheAlmightyGuru. If you find this information helpful in a project you're working on, please give credit where credit is due. (A link back to this wiki would be nice too!)