Ultima I Tile Graphic Format
Format type | Image |
---|---|
Hardware | CGA, EGA, Tandy |
Colour depth | 2-bit (CGA), 4-bit (EGA, Tandy) |
Minimum size (pixels) | 8×8 |
Maximum size (pixels) | |
Palette | Default CGA, EGA |
Plane count | 1 |
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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|
|
|
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!)