Swords of Glass graphic format

From ModdingWiki
Jump to navigation Jump to search
Swords of Glass graphic format
Swords of Glass graphic format.png
Format typeImage
HardwareCGA
Colour depth2-bit (CGA)
Minimum size (pixels)1×1
Maximum size (pixels)30×50
PaletteDefault CGA (0)
Plane count2
Transparent pixels?Yes
Hitmap pixels?No
Games

Swords of Glass's graphics are stored in SHAPES.DAT, CSHAPES.DAT, SHAPES1.DAT, CSHAPES1.DAT, and DOORS.DAT. Shapes contains the player graphics and all monster graphics, CShapes contains environment objects like chests and stairs, Doors contains wall graphics like doorknobs, gargoyles, and keyholes. There are 48 objects in Shapes, 9 in CShapes, and 71 in Doors. Shapes1 and CShapes1 contain the smaller versions of these graphics when they're seen in the distance and have the same number of shapes in each. Doors contains three sizes of each object. Small and medium exists for facing, left, and right, but there is only a large version of the facing graphic. The number of graphics stored in each file is stored in the COM executable, so adding more graphics would be more extensive hack. The Swords of Glass monster format specifies which graphic each monster will use. Both players use the same graphics in the dungeon, but player 2 appears to be XORed.

The format of Shapes1, CShapes1, and Doors is pretty straight-forward. Each record is a single Shape Data (header and CGA data) padded to the record size (468 bytes for shapes, 78 for Doors). These graphics do not support transparency.

The format in Shapes and CShapes is more complex because they support transparency. Each record in the begins with a 60-byte Shape Map for transparency, then has 60 Shape Data records corresponding to each byte in the Shape Map. Each Shape Data record is a 5x5 pixel block used to build up the larger 6x10 blocks image, resulting in a 30x50 pixel graphic. Each Shape Data record is padded out to 26 bytes.

Each Shape Data record has a header, a CGA bit stream where two bits represent a pixel, and padding to fill out the size of the record. The data is not even remotely optimized. Pixel data is stored even for transparent blocks, and, some of the pixel data is redundant because it's based on 4-pixel increments even when they aren't needed. For example, a 5x5 image (25 pixels, and thus 50 bits (7 bytes in 2-bit color) ends up requiring 10 bytes each time, in addition to all the wasted padding used.

The pixel blocks have a lot of what appears to be unused data. The padding following each record appears to be uncleared memory when the files were saved including snippets of source code (the same garbage can be seen in the non-graphics data files). Each Shape Data record begins with a 16-bit integer that is always 2. My guess is that this is the number of bits per pixel, but changing it doesn't appear to affect how the graphic is drawn by the game, so it's probably ignored. While the height and width are used to draw the Shape1 and CShape1 images, the 5x5 blocks of the Shape and CShape graphics must be hard-coded because changing them doesn't affect how they're drawn in the game.

Although the format's header technically allows images up to 65,536×65,536, the record size and game engine constrains them images to much smaller areas, and the largest monster ever drawn in the game is 30×50 pixels, and the largest wall graphic is 12×12.

Shape Map

Each record in Shape and CShape files begin with a shape map which it uses for transparency. The shape map is 60 bytes representing a 6x10 rectangle of boxes stored top-to-bottom, left-to-right. A value of 0x01 indicates the pixel area should be drawn, a 0x00 indicates it should not be drawn (i.e., transparent).

A Shape Map is always followed by 60 Shape Data records of size 5x5 pixels and padded out to 26 bytes.

Data type Description
BYTE[60] Transparent Transparency flag (00 - Yes, 01 - No)

Shape Data

This is the actual graphic data. In Shape1, CShape1, and Doors, this is a single graphic image. In Shape and CShape, there will be 60 blocks that make up a single graphic.

Data type Description
UINT16LE Unknown Always 0x02 0x00. Doesn't appear to be used. Perhaps it was meant to be the number of bits-per-pixel the format uses?
UINT16LE Width Width of the image.
UINT16LE Height Height of the image.
BYTE[ceiling(width / 4) × height] CGA bit stream CGA graphic data. When converted to pixels, it is drawn left-to-right, bottom-to-top.
BYTE[] Padding Extra data to pad out the record. Shape/CShape use 26 byte blocks, Shape1/CShape1 use 468 bytes, Door uses 78 bytes.

Source Code

Shape Viewer

This FreeBASIC program will cycle through all of the shapes in the specified file and draw them with the transparent sections noted. Press any key to view the next shape.

ScreenRes 320, 200, 32

Dim As UByte Map(0 To 47, 0 To 59)
Dim As UByte BlockData(0 To 9)
Dim As Integer Pixels(0 To 39)
Dim As UByte Buffer
Dim As Integer X, Y, I, MapNo, Pixel, BX, BY, ShapeNo, PixelColor
Dim As String BinText

Open "Shapes.dat" For Binary As #1

For ShapeNo = 0 To 47
	Locate 1, 20: Print "Shape #: " + Str(ShapeNo)
	
	' Load the map of the shape blocks.
	' The map is a rectangle 6 units wide and 10 units tall.
	' It is stored from top to bottom, right to left.
	' Those map blocks that are 1 will be drawn with pixels in them,
	' those that are 0 are transparent.
	For MapNo = 0 To 59
		Get #1, , Buffer
		If Eof(1) Then
			End
		End If
		Map(ShapeNo, MapNo) = Buffer
	Next MapNo
	
	' There are 60 blocks of data, corresponding to the 60 blocks in the map.
	' Those blocks that are transparent still have space reserved in the file, 
	' So the file is a lot larger than it needs to be.
	BX = 0: BY = 0
	For MapNo = 0 To 59
		' Each block's header is 0x02 0x00, 0x05 0x00, 0x05 0x00.
		' This is an unknown value, then the width and height.
		' But the game engine hard-codes these values, so we can skip them.
		For I = 0 To 5
			Get #1, , Buffer
		Next I
		
		' The next 10 bytes are a bitstream of CGA graphic data for a 5x5 pixel block.
		For I = 0 To 9
			Get #1, , Buffer
			BlockData(I) = Buffer
		Next I

		' The next 10 bytes are different for each block, but they're identical for each shape.
		' They appear to be padding and aren't needed to draw the graphic, so we throw them away.
		For I = 0 To 9
			Get #1, , Buffer
		Next I

		If Map(ShapeNo, MapNo) = 0 Then
			' This section will be transparent. Draw a box to show it.
			Line(BX, BY)-(BX + 4, BY + 4), RGB(31, 31, 31), B
		Else
			' Convert the bitstream into CGA pixels.
			Pixel = 0
			For I = 0 To 9
				BinText = Bin(BlockData(I), 8)
				For X = 1 To 8 Step 2
					If Mid(BinText, X, 2) = "00" Then PixelColor = RGB(0, 0, 0)
					If Mid(BinText, X, 2) = "01" Then PixelColor = RGB(0, 170, 0)
					If Mid(BinText, X, 2) = "10" Then PixelColor = RGB(170, 0, 0)
					If Mid(BinText, X, 2) = "11" Then PixelColor = RGB(170, 85, 0)
				
					Pixels(Pixel) = PixelColor
					Pixel = Pixel + 1
				Next X
			Next I
			
			' Draw the CGA pixels in the 5x5 block.
			' Pixels are drawn from left to right, bottom to top.
			' The bitsteam stores redundant data pulled from the next block.
			X = 0
			Y = 4
			For I = 0 To 39
				' Skip redundant data.
				If X < 5 Then
					PSet(BX + X, BY + Y), Pixels(I)
				End If
				
				X = X + 1
				If X = 8 Then
					X = 0
					Y = Y - 1
				End If
			Next I
		End If

		' Increase to the next block.
		BY = BY + 5
		If BY = 50 Then
			BY = 0
			BX = BX + 5
		End If
	Next MapNo

	Sleep
	Cls
Next ShapeNo

Shape1 Viewer

This FreeBASIC program will draw all of the Shape1 graphics.

ScreenRes 1000, 600, 32

Open "SHAPES1.DAT" For Binary As #1

Dim As UByte BufferByte
Dim As UShort Unknown, ShapeWidth, ShapeHeight
Dim As Integer BytesWide, X, Y, I, Pixels, PixelColor, ShapeSize, ShapeNo
Dim As UByte ShapeData(0 To 999)
Dim As Integer PixelData(0 To 9999)
Dim As String BinText

For ShapeNo = 0 To 47
	Cls
	
	Get #1, , Unknown		' Possibly bits-per-pixel?
	Get #1, , ShapeWidth
	Get #1, , ShapeHeight

	If Eof(1) Then End

	Locate 1, 20: Print "Shape: " + Str(ShapeNo)
	Locate 3, 20: Print "Width: " + Str(ShapeWidth)
	Locate 4, 20: Print "Height: " + Str(ShapeHeight)

	' Determine how many bytes it will take to hold a single row in 2-bit color graphics.
	' We use this odd way of rounding up because FreeBASIC doesn't have a Ceiling function.
	If Frac(ShapeWidth / 4) > 0 Then
		BytesWide = Int(ShapeWidth / 4) + 1
	Else
		BytesWide = Int(ShapeWidth / 4)
	End If
	
	' Determine how many bytes of CGA data we have to read for this image.
	ShapeSize = BytesWide * ShapeHeight

	Locate 5, 20: Print "Bytes Per Row: " + Str(BytesWide)
	Locate 6, 20: Print "Total Size: " + Str(ShapeSize)

	' Load the bitstream data.
	I = 0
	For I = 0 To ShapeSize - 1
		Get #1, , BufferByte
		ShapeData(I) = BufferByte
	Next I

	' Convert the bitstream into CGA pixels.
	Pixels = 0
	For I = 0 To ShapeSize - 1
		BinText = Bin(ShapeData(I), 8)
		For X = 1 To 8 Step 2
			If Mid(BinText, X, 2) = "00" Then PixelColor = RGB(0, 0, 0)
			If Mid(BinText, X, 2) = "01" Then PixelColor = RGB(0, 170, 0)
			If Mid(BinText, X, 2) = "10" Then PixelColor = RGB(170, 0, 0)
			If Mid(BinText, X, 2) = "11" Then PixelColor = RGB(170, 85, 0)
		
			PixelData(Pixels) = PixelColor
			Pixels = Pixels + 1
		Next X
	Next I

	' Draw the pixels from left-to-right, bottom-to-top.
	Y = ShapeHeight - 1
	X = 0
	For I = 0 To Pixels - 1
		PSet (X, Y), PixelData(I)
		
		X = X + 1
		If X = BytesWide * 4 Then
			X = 0
			Y = Y - 1
		End If
	Next I

	' Records are exactly 468 bytes, regardless of whether the shape uses them all.
	' So, jump ahead to the next record.
	Get #1, (ShapeNo + 1) * 468, BufferByte

	Sleep
Next ShapeNo

Lookups

This is a quick lookup of all the shapes in the game files by default.

Shapes CShapes Doors
00 - Player Towards
01 - Player Right
02 - Player Away
03 - Player Left
04 - Axe Man
05 - Archer
06 - Swordsman
07 - Wizard
08 - Smoke
09 - Three Flies
10 - Dog
11 - Snake
12 - Ghost
13 - Shark
14 - Large Cat
15 - Rhino
16 - Ooze
17 - Slug
18 - Skeleton
19 - Dragon
20 - Tree
21 - Weed
22 - Tyrannosaurus
23 - Lion
24 - Pterodactyl
25 - Cockroach
26 - Two-Headed Mutant
27 - Bat
28 - Rabbit
29 - Elephant
30 - Alligator
31 - Rubble Monster
32 - Jellyfish
33 - Scorpion
34 - Ant
35 - Bear
36 - Lizard
37 - Octopus
38 - Mosquito
39 - Golem
40 - Demon
41 - Spider
42 - Gnats
43 - Giraffe
44 - Jack-o-lantern
45 - Tomato
46 - Kangaroo
47 - Storm
00 - Chest
01 - Stairs Up
02 - Stairs Down
03 - Fire
04 - Rubble
05 - Stalactites
06 - Statue
07 - Rope
08 - Tombstone
00 - Door Knob
01 - Green Keyhole
02 - Torch
03 - Chalk Mark
04 - Spider
05 - Gargoyle
06 - Leech
07 - Jewel
08 - Red Keyhole
09 - Gold Keyhole

Credits

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