Ultima I Tile Graphic Format

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

Ultima I's tiled graphics are store across several files, each with a *.bin extension and a file prefix of "cga," "ega," or "t1k" (Tandy 1000). The ???Town.bin files are used with the tcd.bin file and the ???Tiles.bin files are used with the map.bin file. Technically speaking, there are several unique file formats being used across the various files, but each is described on this page for brevity.

Image

File Formats
File Encoding Tile Count Tile Size
CGAFight.bin CGA linear? 28 24×19
EGAFight.bin Monochromatic linear? 56 24×19
T1KFight.bin EGA Linear 14 24×19
CGAMond.bin CGA Linear 19 16×16
EGAMond.bin EGA Row-planar 19 16×16
T1KMond.bin EGA Linear 19 16×16
CGASpace.bin CGA linear? 48 32×19
EGASpace.bin Monochromatic linear? 96 32×19
T1KSpace.bin EGA Linear 24 32×19
CGATiles.bin CGA Linear 52 16×16
EGATiles.bin EGA Row-planar 52 16×16
T1KTiles.bin EGA Linear 52 16×16
CGATown.bin CGA Linear 51 8×8
EGATown.bin EGA Row-planar 51 8×8
T1KTown.bin EGA Linear 51 8×8

None of the formats use a header, each is uncompressed. The encoding type, display type, number of tiles, and size of the tiles are each determined by the EXE, so modification is limited. The table to the right gives details for each of the files and their tile attributes. The ???Fight.bin and ???Space.bin file each duplicate the tiles (and, depending on the encoding, they may be duplicated again and again), with the duplicates having their pixel data nudged to the right. The reason for this is unknown. It's possible this has something to do with the tiles being encoded in a graphic-planar ordering for each tile, but the pixel-nudging makes this seem unlikely.

Data type Name Description
BYTE pixels Binary representation of 2, 4, or 8 pixels depending on the encoding.

CGA Linear

The CGA files uses linear ordering. Four pixels are stored 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, and repeated for each tile. All CGA files use this encoding, however, the CGAFight.bin and CGASpace.bin replicate each tile four times, each nudging the pixel data to the right once for each duplicate.

EGA Row-planar

Some EGA files use row-planar ordering in the color order of: blue, green, red, intensity. The number of bytes for each color is equal to the tile's width divided by 8. So, if the tile is 8 pixels wide, the color data will be stored as BGRI, but it the tile is 16 pixels wide, the color data will be stored as BBGGRRII. Once the color information is loaded, the pixels are drawn left-to-right, top-to-bottom, and repeated for each tile. All EGA files use this format except for EGAFight.bin and EGASpace.bin which use a monochromatic linear encoding.

EGA Linear

All the Tandy files use linear ordering, storing two pixels per byte. The high nibble represents the first pixel, the low nibble represents the second pixels. The graphic data is stored left-to-right, top-to-bottom, and repeated for each tile. All Tandy files use this encoding, however, the T1KFight.bin and T1KSpace.bin replicate each tile twice, the duplicate nudges the pixel data to the right once.

Monochromatic Linear

The EGAFight.bin and EGASpace.bin files use a monochromatic linear encoding. Each byte represents 8 pixels drawn in white. Both files replicate each tile eight times, each nudging the pixel data to the right once for each duplicate.

Tile Data

This is a lookup for the various tiles found in each of the files.

Fight.bin Mond.bin Space.bin Tile.bin Town.bin
Space combat Final battle with Mondain Space docking (Earth and Sun are not in this set) Over word map Town map
Id Tile
0x00-0x01 Zoom x1
0x02-0x03 Zoom x2
0x04-0x05 Zoom x3
0x06-0x07 Zoom x4
0x08-0x09 Zoom x5
0x0A-0x0B Zoom x6
Id Tile
0x00 Time Machine
0x01-0x02 Mondain
0x03 Mondain, slain
0x04 Wall
0x05-0x09 Gem of Immortality
0x0A-0x0C Bat
0x0D-0x12 Explosion
Id Tile
0x00-0x07 Shuttle
0x08-0x0F Left Ship
0x10-0x17 Bottom Ship
Id Tile
0x00 Water
0x01 Grass
0x02 Forest
0x03 Mountain
0x04-0x05 Castle
0x06 Signpost
0x07-0x08 Town
0x09 Dungeon Entrance
0x0A Player
0x0B Horse
0x0C Cart
0x0D Raft
0x0E-0x0F Frigate
0x10 Aircar
0x11 Shuttle
0x12 Time Machine
0x13-0x14 Ness Monster
0x15-0x16 Giant Squid
0x17-0x18 Dragon Turtle
0x19-0x1A Pirate Ship
0x1B-0x1C Hood
0x1D-0x1E Bear
0x1F-0x20 Hidden Archer
0x21-0x22 Dark Knight
0x23-0x24 Evil Trent
0x25-0x26 Thief
0x27-0x28 Orc
0x29-0x2A Knight
0x2B-0x2C Necromancer
0x2D-0x2E Evil Ranger
0x2F-0x30 Wandering Warlock
0x31 Unused Dog Head
Id Tile
0x00 Solid Wall
0x01 Floor
0x02 Water
0x03 Water Corner - Top-Left
0x04 Water Corner - Bottom-Left
0x05 Water Corner - Top-Right
0x06 Water Corner - Bottom-Right
0x07 Water Diagonal - Bottom-Right
0x08 Water Diagonal - Bottom-Left
0x09 Tree - Small
0x0A Tree - Big
0x0B Counter - Left-Right
0x0C Counter - Top-Bottom
0x0D Counter - Top
0x0E Counter - Bottom
0x0F Counter - Left
0x10 Counter - Right
0x11 Person - Guard
0x12 Person - Player
0x13 Person - Jester 1
0x14 Person - King
0x15 Person - Merchant
0x16 Person - Prisoner
0x17 Brick Wall
0x18-0x31 Letters A-Z
0x32 Person - Jester 2

Source Code

Tile Viewer

The following FreeBASIC code will load and display all of the Ultima I tiles for each encoding type.

' Draws any of the Ultima 1 tile files.

' Change this to the path and file name you want to view.
Dim As String DataPath = "H:\DOS\Ultima"
Dim As String FileName = "T1KFight.bin"

Dim As UByte TileCount
Dim As UByte TileWidth
Dim As UByte TileHeight

' Determine the display type, file encoding, tile count, and tile size based on the file name.
Dim As String TileType = UCase(Mid(FileName, 4, 4))
Dim As String DisplayType = UCase(Left(FileName, 3))
Dim As String Encode
Select Case TileType
Case "FIGH"
    TileWidth = 24
    TileHeight = 19
    TileCount = 14

    Select Case DisplayType
    Case "CGA"
        Encode = "CGA Linear"
        TileCount = TileCount * 2
    Case "EGA"
        Encode = "Monochrome"
        TileCount = TileCount * 4
    Case "T1K"
        Encode = "EGA Linear"
    End Select
Case "MOND"
    TileWidth = 16
    TileHeight = 16
    TileCount = 19

    Select Case DisplayType
    Case "CGA"
        Encode = "CGA Linear"
    Case "EGA"
        Encode = "EGA Row-planar"
    Case "T1K"
        Encode = "EGA Linear"
    End Select
Case "SPAC"
    TileWidth = 32
    TileHeight = 19
    TileCount = 24

    Select Case DisplayType
    Case "CGA"
        Encode = "CGA Linear"
        TileCount = TileCount * 2
    Case "EGA"
        Encode = "Monochrome"
        TileCount = TileCount * 4
    Case "T1K"
        Encode = "EGA Linear"
    End Select
Case "TILE"
    TileWidth = 16
    TileHeight = 16
    TileCount = 52

    Select Case DisplayType
    Case "CGA"
        Encode = "CGA Linear"
    Case "EGA"
        Encode = "EGA Row-planar"
    Case "T1K"
        Encode = "EGA Linear"
    End Select
Case "TOWN"
    TileWidth = 8
    TileHeight = 8
    TileCount = 51

    Select Case DisplayType
    Case "CGA"
        Encode = "CGA Linear"
    Case "EGA"
        Encode = "EGA Row-planar"
    Case "T1K"
        Encode = "EGA Linear"
    End Select
End Select

Dim As UByte X
Dim As UByte Y
Dim As UByte Pixel
Dim As UByte Pixels
Dim As UByte TileNumber
Dim As UShort OffsetX = 0
Dim As UShort OffsetY = 0
Dim As UByte Offset
Dim As UByte ColorBlock
Dim As UByte Plane

Open DataPath + Chr(92) + FileName For Binary As #1

' Determine the decoding type based on the display prefix.
Select Case Encode
Case "CGA Linear"
    ' CGA Linear stores 4 pixels per byte. Bits 7 and 6 determine the first color, 
    ' bits 5 and 4 determined the next color, and so on.
    
    Screen 1
    Color 0, 1      ' Low intensity cyan/magenta/white palette.

    For TileNumber = 0 To (TileCount - 1)
        For Y = 0 To (TileHeight - 1)
            For X = 0 To (TileWidth / 4) - 1
                Get #1, , Pixels
                
                ' Determine the four pixels from this one byte.
                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
        
                    ' Plot the pixel.
                    PSet(OffsetX + (X * 4) + Offset, OffsetY + Y), Pixel
                    ColorBlock = ColorBlock - 2
                Next Offset
            Next X
        Next Y
        
        OffsetX = OffsetX + TileWidth
        If (OffsetX + TileWidth) > 319 Then
            OffsetX = 0
            OffsetY = OffsetY + TileHeight
        End If
    Next TileNumber
    
Case "EGA Row-planar"
    ' Row-planar ordering stores planes of data in the order of blue, green, red and 
    ' intensity. The number of bytes for each color is the tile's width divided by 8.
    ' So, if the tile is 24 pixels wide, there will be three bytes of blue, the three 
    ' of green and so on.

    Screen 7

    Dim As UByte PlanarBytes = TileWidth / 8
    Dim As UByte Blue(0 To PlanarBytes)
    Dim As UByte Green(0 To PlanarBytes)
    Dim As UByte Red(0 To PlanarBytes)
    Dim As UByte Intensity(0 To PlanarBytes)

    For TileNumber = 0 To (TileCount - 1)
        For Y = 0 To (TileHeight - 1)
            ' Read the color planes.
            For Pixels = 0 To (PlanarBytes - 1)
                Get #1, , Blue(Pixels)
            Next Pixels
            For Pixels = 0 To (PlanarBytes - 1)
                Get #1, , Green(Pixels)
            Next Pixels
            For Pixels = 0 To (PlanarBytes - 1)
                Get #1, , Red(Pixels)
            Next Pixels
            For Pixels = 0 To (PlanarBytes - 1)
                Get #1, , Intensity(Pixels)
            Next Pixels

            ' Render the pixels.
            For X = 0 To (PlanarBytes - 1)
                Offset = 7
                For ColorBlock = 0 To 7
                    Pixel = 0
                    If Bit(Blue(X), Offset) = -1 Then
                        Pixel = Pixel + 1
                    End If
                    If Bit(Green(X), Offset) = -1 Then
                        Pixel = Pixel + 2
                    End If
                    If Bit(Red(X), Offset) = -1 Then
                        Pixel = Pixel + 4
                    End If
                    If Bit(Intensity(X), Offset) = -1 Then
                        Pixel = Pixel + 8
                    End If
                    
                    PSet(OffsetX + (X * 8) + ColorBlock, OffsetY + Y), Pixel
        
                    Offset = Offset - 1
                Next ColorBlock
            Next X
        Next Y
        
        OffsetX = OffsetX + TileWidth
        If (OffsetX + TileWidth) > 319 Then
            OffsetX = 0
            OffsetY = OffsetY + TileHeight
        End If
    Next TileNumber

Case "EGA Linear"
    ' EGA linear stores 2 pixels per byte. The hi nibble is the first 
    ' color, the lo nibble is the second.
    
    Screen 7
    
    Dim As UByte HiNibble
    Dim As UByte LoNibble
    
    For TileNumber = 0 To (TileCount - 1)
        For Y = 0 To (TileHeight - 1)
            For X = 0 To (TileWidth / 2) - 1
                Get #1, , Pixels
        
                HiNibble = Pixels SHR 4
                HiNibble = HiNibble And &h0F
                LoNibble = Pixels AND &h0F
                
                PSet(OffsetX + (X * 2), OffsetY + Y), HiNibble
                PSet(OffsetX + (X * 2) + 1, OffsetY + Y), LoNibble
            Next X
        Next Y
        
        OffsetX = OffsetX + TileWidth
        If (OffsetX + TileWidth) > 319 Then
            OffsetX = 0
            OffsetY = OffsetY + TileHeight
        End If
    Next TileNumber

Case "Monochrome"
    Screen 7
    
    For TileNumber = 0 To (TileCount - 1)
        For Y = 0 To (TileHeight - 1)
            For X = 0 To (TileWidth / 8) - 1
                Get #1, , Pixels

                Offset = 7
                For ColorBlock = 0 To 7
                    If Bit(Pixels, Offset) = -1 Then
                        Pixel = 15
                    Else
                        Pixel = 0
                    End If
                    
                    PSet(OffsetX + (X * 8) + ColorBlock, OffsetY + Y), Pixel
                    Offset = Offset - 1
                Next ColorBlock
            Next X
        Next Y

        OffsetX = OffsetX + TileWidth
        If (OffsetX + TileWidth) > 319 Then
            OffsetX = 0
            OffsetY = OffsetY + TileHeight
        End If
    Next TileNumber
End Select

Locate 24, 1: Print Encode
Sleep

Credits

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