Ultima I Full Screen Graphic Format

From ModdingWiki
Jump to navigation Jump to search
Ultima I Full Screen Graphic Format
Ultima I Full Screen Graphic Format.png
Format typeImage
HardwareCGA, EGA, Tandy
Colour depth2-bit (CGA), 4-bit (EGA, Tandy)
Minimum size (pixels)320×200
Maximum size (pixels)320×200
PaletteDefault CGA, External EGA/Tandy
Plane count1
Transparent pixels?No
Hitmap pixels?No
Games

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.

Image

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.

Data type Name Description
BYTE pixels Binary representation of 2, 4, or 8 pixels.

CGA

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.

Cross-Over Palette
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:

Ultima I EGA Title Without Palette Cross-over.png

Monochrome

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.

Source Code

Viewer

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

Credits

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!)