Visage Format

From ModdingWiki
Jump to: navigation, search
Visage Format
Visage Format.png
Format typeImage
Colour depth8-bit (VGA)
Minimum size (pixels)0×0
Maximum size (pixels)65535×65535
Plane count1
Transparent pixels?Yes
Hitmap pixels?No

The Visage Format is a graphic format that can store multiple images and palettes. It is used in The Lost Files of Sherlock Holmes: The Case of the Serrated Scalpel and The Lost Files of Sherlock Holmes: The Case of the Rose Tattoo to store various backgrounds and sprites. Each block of data can be uncompressed or use RLE. Visage files use the *.vgs extension, though some use *.lbv.

File format

Although the format can contain multiple blocks, there is no header to tell how many data blocks exist. You just need to check for End of File to know when you're done reading.

Data type Name Description
UINT16LE Width Data width.
UINT16LE Height Data height.
UINT8 Unknown 0 usually, disasssembly shows this can control scaling somehow.
UINT8 Compressed 0 - Uncompressed, 1 - RLE.
BYTE xOffset Added to drawing position to offset an image. Works as an anchor to allow sprite

frames with varying dimensions to 'line up'.

BYTE yOffset See X Offset.
BYTE[Width×height] Data Palette or graphic data.

With the header loaded, you know to read width×height bytes of data which can either be palette or graphic data.


Palette data can be identified because it begins with "VGS palette" followed by a standard 256 index, 6-bit color depth VGA Palette. For some reason the palette data is given a height of 1 (2 rows), so you have to concatenate the data to read the values properly.


Graphic data is implied if the data block isn't a palette. The graphic data is 8-bit VGA data, but, depending on the compression flag, can be either linear VGA data or use a type of RLE as described below.

RLE Compression

The compressed data in both The Lost Files of Sherlock Holmes: The Case of the Serrated Scalpel and the sequel The Lost Files of Sherlock Holmes: The Case of the Rose Tattoo uses the following header:

DataType Name Description
UINT16LE size Size of the compressed data.
BYTE repeatMarker Signals a repeated byte. Only used in the first game.

In the first game, decompress as follows:

  1. Read a BYTE as testByte.
    • If testByte == repeatMarker, read a BYTE as val, read a UINT8 as num, and copy num vals to output.
    • Else add testByte to output.
  2. Loop until out of data.

The second game's compression scheme divides each row into alternating chunks of transparent and opaque pixels. The decompression of each row follows this logic:

For each row:

  1. Read a UINT8 as skipSize.
  2. Add skipSize × 0xff to output. 0xff is the transparent colour index.
  3. Read a UINT8 as chunkSize.
  4. Copy chunkSize bytes from input to output.
  5. Loop until the end of the row is reached.

Source Code

The following FreeBASIC code will load through each block of a Visage file and display the palette or graphic data. It doesn't yet properly decode the RLE compressed graphics.

' Visage File Viewer.
' Visage files contain 1 or more blocks of data that can include either palette or graphic data.
' Each block begins with a header of four 16-bit integers: width, height, RLE compression, and unknown.
' Blocks containing palette data begin with "VGA palette", all other blocks are graphic data.
' Palette blocks are typical 8-bit VGA palettes, 256 indexes, each with three 6-bit color values.
' Graphic blocks are raw 8-bit indexed VGA pixel data.
' The file path to the palette file you want to open.
Dim As String VGSFile = "H:\Programs\LIB Extractor\vgs\bigmap.vgs"
' Open the Visage file.
If Open(VGSFile For Binary Access Read As #1) <> 0 Then
    Print "File: " + VGSFile + " not found!"
End If
Dim Y As Integer, X As Integer
Dim Index As UByte, Length As UByte
Dim XSize As UShort, YSize As UShort
Dim RLEFlag As UShort, Unknown As UShort
Dim BlockSize As UInteger
Dim As String PaletteCheck = ""
Dim ColorIndex As UShort, ColorOffset As UShort
Dim Red As UByte, Green As UByte, Blue As UByte
Dim As UByte Block = 0
Dim Offset As UInteger, Delta As UShort, I As UShort, FullLength As UInteger
Screen 13
    ' Load the block header.
    Get #1, , XSize
    Get #1, , YSize
    Get #1, , RLEFlag
    Get #1, , Unknown
    'If XSize = 389 And YSize = 1 Then
    '    ' Trap for palette which doesn't store the correct size to load.
    '    XSize = 780
    'End If
    BlockSize = (XSize + 1) * (YSize + 1)
    Print "    Block #: " + Str(Block)
    Print "X, Y (Size): " + Str(XSize) + ", " + Str(YSize) + " (" + Str(BlockSize) + " bytes)"
    Print " Compressed: " + Str(RLEFlag)
    Print "    Unknown: " + Str(Unknown)
    ' Create an array for the block data.
    ReDim VGSData(0 To Blocksize) As UByte
    ' Load this block's data into the array.
    For Y = 0 To YSize
        For X = 0 To XSize
            Get #1, , VGSData(Y * XSize + X)
        Next X
    Next Y
    ' Check for palette.
    PaletteCheck = ""
    For X = 0 To 11
        PaletteCheck = PaletteCheck + Chr(VGSData(X))
    Next X
    If PaletteCheck = "VGA palette" + Chr(26) Then
        Print "       Type: Palette Data"
        ' Loop through all of the colors.
        ColorOffset = 12
        ColorIndex = 0
            ' Read the color attributes from the file.
            Red   = VGSData(ColorOffset)
            Green = VGSData(ColorOffset + 1)
            Blue  = VGSData(ColorOffset + 2)
            Red   = Red   * 4
            Green = Green * 4
            Blue  = Blue  * 4
            ' Change the palette.
            Palette ColorIndex, Red, Green, Blue
            Line(ColorIndex, 0)-(ColorIndex, 199), ColorIndex
            ColorIndex = ColorIndex + 1
            ColorOffset = ColorOffset + 3
        Loop Until ColorOffset >= BlockSize
        Print "       Type: Graphic Data"
        ' If it's not a palette, it's graphic data.
        If RLEFlag = 0 Then
            ' Uncompressed image.
            For Y = 0 To YSize
                For X = 0 To XSize
                    PSet(X, Y), VGSData(Y * XSize + X)
                Next X
            Next Y
            ' Compressed image.
            Y = 0
            X = 0
            FullLength = VGSData(1) * 256 + VGSData(0)      ' Data Stream Length
            ' Unknown: Always FE FE.
            Offset = 4
            ' To Do: Decode RLE.
        End If
    End If
    Block = Block + 1
Loop While Not EOF(1)
Close #1


This file format was reverse engineered by TheAlmightyGuru. The RLE compression was reverse engineered by Ceidwad. 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!)