Ultima II Map Format
Ultima II: Revenge of the Enchantress uses dozens of maps for planet over worlds and towns, those maps with numbers ending in 0 are planets, those ending in 1, 2, or 3 are towns, those ending in 4 or 5 are dungeons. Planet and town maps are 64×66 tiles, which yields 1,024×1,056 pixels. Dialogue from NPCs is stored in separate files in the Ultima II Talk Format.
The tiles used to draw the maps are stored in the ULTIMAII.EXE file. Interestingly, the game rewrites the map files as the game runs, saving the location of NPCs and monsters directly in the map files.
For planets and towns, the map data is a string of 4,224 bytes where each byte represents a tile id. The values in the map match up with the tiles in the ULTIMAII.EXE, only they're all multiplied by 4, so, the tile number from map file must be divided by 4 to get the correct file in the tile lookup.
|UINT8[4,224]||Tile Id||The tile id for each tile in the map.|
Remember that the maps store the tile id ×4. So, a mountain, which is tile id 4, will be stored as 16 (0x10).
|Tile Id||Tile Description|
|31||Empty Counter / Space|
|40||I / Door|
|58||Counter End, Right|
|59||Counter End, Left|
The map file does not store which specific NPC, castle, town, dungeon, or signpost is at which location.
This FreeBASIC program displays game maps with the proper tile set.
' Renders the specified Ultima II map. #include once "fbgfx.bi" ' Change to your Ultima II path, and set the map file you want to view. Dim As String DataPath = "H:\DOS\Ultima2" Dim As String MapFile = "mapx20" Dim As fb.Image Ptr TileCGA(0 To 63) Dim As UByte X Dim As UByte Y Dim As Integer 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 ' Graphic tile data is stored in the EXE. Open DataPath + Chr(92) + "ultimaii.exe" For Binary As #1 Seek 1, 31811 ' Jump to the start of the over world tiles. ScreenRes 1024, 1056, 32 ' Load all of the tiles into memory. For TileNumber = 0 To 63 For Y = 0 To 15 For X = 0 To 3 ' 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. 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 = RGB(170, 170, 170) Else Pixel = RGB(170, 0, 170) End If Else If Bit(Pixels, ColorBlock - 1) = -1 Then Pixel = RGB(0, 170, 170) Else Pixel = RGB(0, 0, 0) End If End If ' Plot the pixel. PSet((X * 4) + Offset, Y), Pixel ColorBlock = ColorBlock - 2 Next Offset Next X Next Y Get #1, , Pixels Get #1, , Pixels ' Load the tile into an image buffer. TileCGA(TileNumber) = ImageCreate(16, 16) Get (0, 0)-(15, 15), TileCGA(TileNumber) Next TileNumber Close #1 ' Open the map file. CLS Open DataPath + Chr(92) + MapFile For Binary As #1 ' Load the entire map, and draw the appropriate tiles. For Y = 0 To 65 For X = 0 To 63 Get #1, , TileNumber TileNumber = TileNumber / 4 Put (X * 16, Y * 16), TileCGA(TileNumber) Next X Next Y Close #1 Sleep
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!)