Task 791: .VSD File Format
Task 791: .VSD File Format
File Format Specifications for .VSD
The .VSD file format is a proprietary binary format used by Microsoft Visio for storing diagram data, including shapes, connections, and metadata. It is based on the Microsoft Compound File Binary File Format (CFBF), also known as Structured Storage or OLE Compound File format. This format provides a file-system-like structure within a single file, allowing multiple streams and storages. Microsoft has not published a complete specification for the Visio-specific internal streams and data structures in .VSD files; however, the underlying CFB format is documented in the [MS-CFB] specification. The format was used in Visio versions up to 2010, after which it was replaced by the XML-based .VSDX format. The intrinsic properties are derived from the CFB header, which is common to all files using this container format, including .VSD.
- List of All Properties Intrinsic to the File Format
The properties are the fields in the 512-byte compound file header, which defines the structure and layout of the file. These are intrinsic to the file format as they determine how the file system-like structure is organized and accessed. The following table lists the properties, their offsets, sizes, and descriptions:
| Property Name | Offset (bytes) | Size (bytes) | Description |
|---|---|---|---|
| Header Signature | 0 | 8 | Identification signature; must be 0xD0 0xCF 0x11 0xE0 0xA1 0xB1 0x1A 0xE1. |
| Header CLSID | 8 | 16 | Reserved class ID; must be all zeroes (CLSID_NULL). |
| Minor Version | 24 | 2 | Version for non-breaking changes; typically 0x003E. |
| Major Version | 26 | 2 | Major version; 0x0003 for 512-byte sectors or 0x0004 for 4096-byte sectors. |
| Byte Order | 28 | 2 | Byte order indicator; 0xFFFE for little-endian. |
| Sector Shift | 30 | 2 | Sector size as power of 2; 0x0009 (512 bytes) or 0x000C (4096 bytes). |
| Mini Sector Shift | 32 | 2 | Mini sector size as power of 2; must be 0x0006 (64 bytes). |
| Reserved | 34 | 6 | Reserved; must be all zeroes. |
| Number of Directory Sectors | 40 | 4 | Count of directory sectors; 0 for major version 3. |
| Number of FAT Sectors | 44 | 4 | Count of File Allocation Table (FAT) sectors. |
| First Directory Sector Location | 48 | 4 | Sector number of the first directory sector. |
| Transaction Signature Number | 52 | 4 | Value for transaction support; typically incremented on writes. |
| Mini Stream Cutoff Size | 56 | 4 | Maximum size for mini streams; must be 0x00001000 (4096 bytes). |
| First Mini FAT Sector Location | 60 | 4 | Sector number of the first mini FAT sector. |
| Number of Mini FAT Sectors | 64 | 4 | Count of mini FAT sectors. |
| First DIFAT Sector Location | 68 | 4 | Sector number of the first Double Indirect FAT (DIFAT) sector. |
| Number of DIFAT Sectors | 72 | 4 | Count of DIFAT sectors. |
| DIFAT Array | 76 | 436 | Array of 109 32-bit integers specifying FAT sector locations. |
- Two Direct Download Links for .VSD Files
- https://download.microsoft.com/download/8/5/a/85a86f6b-8745-4535-8aa5-9a5b694acfd1/MVPSession1SimpleTimeline.vsd (Microsoft sample timeline diagram)
- https://studylib.net/data/1/8/8791185/visio-sample-star-schemas.vsd (Sample star schema diagram for data warehousing)
- Ghost Blog Embedded HTML/JavaScript for Drag-and-Drop .VSD Property Dump
The following is a self-contained HTML snippet with embedded JavaScript that can be embedded in a Ghost blog post. It creates a drag-and-drop area where a user can drop a .VSD file. The script reads the file, parses the 512-byte header, extracts the properties, and displays them on the screen.
- Python Class for .VSD File Handling
The following Python class opens a .VSD file, decodes the header, reads and prints the properties to the console, and includes a method to write (update) the header with modified values if needed.
import struct
import os
class VSDFileHandler:
def __init__(self, filepath):
self.filepath = filepath
self.header = None
self.read_and_decode()
def read_and_decode(self):
with open(self.filepath, 'rb') as f:
header_bytes = f.read(512)
if len(header_bytes) < 512:
raise ValueError("File too small to be a valid .VSD")
self.header = {}
self.header['Signature'] = header_bytes[0:8].hex(' ').upper()
self.header['CLSID'] = header_bytes[8:24].hex(' ').upper()
self.header['Minor Version'] = struct.unpack_from('<H', header_bytes, 24)[0]
self.header['Major Version'] = struct.unpack_from('<H', header_bytes, 26)[0]
self.header['Byte Order'] = struct.unpack_from('<H', header_bytes, 28)[0]
self.header['Sector Shift'] = struct.unpack_from('<H', header_bytes, 30)[0]
self.header['Mini Sector Shift'] = struct.unpack_from('<H', header_bytes, 32)[0]
self.header['Reserved'] = header_bytes[34:40].hex(' ').upper()
self.header['Num Directory Sectors'] = struct.unpack_from('<I', header_bytes, 40)[0]
self.header['Num FAT Sectors'] = struct.unpack_from('<I', header_bytes, 44)[0]
self.header['First Directory Sector'] = struct.unpack_from('<I', header_bytes, 48)[0]
self.header['Transaction Signature'] = struct.unpack_from('<I', header_bytes, 52)[0]
self.header['Mini Stream Cutoff'] = struct.unpack_from('<I', header_bytes, 56)[0]
self.header['First Mini FAT Sector'] = struct.unpack_from('<I', header_bytes, 60)[0]
self.header['Num Mini FAT Sectors'] = struct.unpack_from('<I', header_bytes, 64)[0]
self.header['First DIFAT Sector'] = struct.unpack_from('<I', header_bytes, 68)[0]
self.header['Num DIFAT Sectors'] = struct.unpack_from('<I', header_bytes, 72)[0]
self.header['DIFAT'] = [struct.unpack_from('<I', header_bytes, 76 + i*4)[0] for i in range(109)]
self.print_properties()
def print_properties(self):
print("Extracted .VSD Properties:")
for key, value in self.header.items():
if isinstance(value, list):
print(f"{key}: {[hex(v) for v in value[:5]]}... (109 entries)")
else:
print(f"{key}: {value if isinstance(value, int) else hex(value) if isinstance(value, int) else value}")
def write_header(self, new_header_values=None):
if new_header_values:
for key, value in new_header_values.items():
if key in self.header:
self.header[key] = value
with open(self.filepath, 'r+b') as f:
header_bytes = bytearray(512)
header_bytes[0:8] = bytes.fromhex(self.header['Signature'])
header_bytes[8:24] = bytes.fromhex(self.header['CLSID'])
struct.pack_into('<H', header_bytes, 24, self.header['Minor Version'])
struct.pack_into('<H', header_bytes, 26, self.header['Major Version'])
struct.pack_into('<H', header_bytes, 28, self.header['Byte Order'])
struct.pack_into('<H', header_bytes, 30, self.header['Sector Shift'])
struct.pack_into('<H', header_bytes, 32, self.header['Mini Sector Shift'])
header_bytes[34:40] = bytes.fromhex(self.header['Reserved'])
struct.pack_into('<I', header_bytes, 40, self.header['Num Directory Sectors'])
struct.pack_into('<I', header_bytes, 44, self.header['Num FAT Sectors'])
struct.pack_into('<I', header_bytes, 48, self.header['First Directory Sector'])
struct.pack_into('<I', header_bytes, 52, self.header['Transaction Signature'])
struct.pack_into('<I', header_bytes, 56, self.header['Mini Stream Cutoff'])
struct.pack_into('<I', header_bytes, 60, self.header['First Mini FAT Sector'])
struct.pack_into('<I', header_bytes, 64, self.header['Num Mini FAT Sectors'])
struct.pack_into('<I', header_bytes, 68, self.header['First DIFAT Sector'])
struct.pack_into('<I', header_bytes, 72, self.header['Num DIFAT Sectors'])
for i in range(109):
struct.pack_into('<I', header_bytes, 76 + i*4, self.header['DIFAT'][i])
f.write(header_bytes)
# Example usage: handler = VSDFileHandler('example.vsd')
# To write: handler.write_header({'Transaction Signature': new_value})
- Java Class for .VSD File Handling
The following Java class opens a .VSD file, decodes the header, reads and prints the properties to the console, and includes a method to write the header.
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class VSDFileHandler {
private Path filepath;
private ByteBuffer headerBuffer;
public VSDFileHandler(String filepath) {
this.filepath = Paths.get(filepath);
readAndDecode();
}
private void readAndDecode() {
try (RandomAccessFile raf = new RandomAccessFile(filepath.toFile(), "r")) {
byte[] headerBytes = new byte[512];
raf.readFully(headerBytes);
headerBuffer = ByteBuffer.wrap(headerBytes).order(ByteOrder.LITTLE_ENDIAN);
System.out.println("Extracted .VSD Properties:");
System.out.print("Header Signature: ");
for (int i = 0; i < 8; i++) System.out.print(String.format("%02X ", headerBuffer.get(i)));
System.out.println();
System.out.print("Header CLSID: ");
for (int i = 8; i < 24; i++) System.out.print(String.format("%02X ", headerBuffer.get(i)));
System.out.println();
System.out.println("Minor Version: 0x" + Integer.toHexString(headerBuffer.getShort(24) & 0xFFFF).toUpperCase());
System.out.println("Major Version: 0x" + Integer.toHexString(headerBuffer.getShort(26) & 0xFFFF).toUpperCase());
System.out.println("Byte Order: 0x" + Integer.toHexString(headerBuffer.getShort(28) & 0xFFFF).toUpperCase());
System.out.println("Sector Shift: 0x" + Integer.toHexString(headerBuffer.getShort(30) & 0xFFFF).toUpperCase());
System.out.println("Mini Sector Shift: 0x" + Integer.toHexString(headerBuffer.getShort(32) & 0xFFFF).toUpperCase());
System.out.print("Reserved: ");
for (int i = 34; i < 40; i++) System.out.print(String.format("%02X ", headerBuffer.get(i)));
System.out.println();
System.out.println("Number of Directory Sectors: " + headerBuffer.getInt(40));
System.out.println("Number of FAT Sectors: " + headerBuffer.getInt(44));
System.out.println("First Directory Sector Location: " + headerBuffer.getInt(48));
System.out.println("Transaction Signature Number: " + headerBuffer.getInt(52));
System.out.println("Mini Stream Cutoff Size: " + headerBuffer.getInt(56));
System.out.println("First Mini FAT Sector Location: " + headerBuffer.getInt(60));
System.out.println("Number of Mini FAT Sectors: " + headerBuffer.getInt(64));
System.out.println("First DIFAT Sector Location: " + headerBuffer.getInt(68));
System.out.println("Number of DIFAT Sectors: " + headerBuffer.getInt(72));
System.out.print("DIFAT Array (sample): ");
for (int i = 0; i < 5; i++) System.out.print(Integer.toHexString(headerBuffer.getInt(76 + i*4)).toUpperCase() + " ");
System.out.println("... (109 entries)");
} catch (Exception e) {
e.printStackTrace();
}
}
public void writeHeader() {
try (FileChannel channel = FileChannel.open(filepath, StandardOpenOption.WRITE)) {
headerBuffer.position(0);
channel.write(headerBuffer);
} catch (Exception e) {
e.printStackTrace();
}
}
// Example to modify: headerBuffer.putInt(52, newTransactionValue); then call writeHeader()
public static void main(String[] args) {
new VSDFileHandler("example.vsd");
}
}
- JavaScript Class for .VSD File Handling
The following JavaScript class can be used in a Node.js environment (requires 'fs' module) to open a .VSD file, decode the header, read and print the properties to the console, and write the header.
const fs = require('fs');
class VSDFileHandler {
constructor(filepath) {
this.filepath = filepath;
this.header = null;
this.readAndDecode();
}
readAndDecode() {
const headerBytes = fs.readFileSync(this.filepath, { encoding: null }).slice(0, 512);
if (headerBytes.length < 512) {
throw new Error('File too small to be a valid .VSD');
}
const view = new DataView(headerBytes.buffer);
this.header = {};
this.header.signature = Array.from(headerBytes.slice(0, 8)).map(b => b.toString(16).padStart(2, '0')).join(' ');
this.header.clsid = Array.from(headerBytes.slice(8, 24)).map(b => b.toString(16).padStart(2, '0')).join(' ');
this.header.minorVersion = view.getUint16(24, true);
this.header.majorVersion = view.getUint16(26, true);
this.header.byteOrder = view.getUint16(28, true);
this.header.sectorShift = view.getUint16(30, true);
this.header.miniSectorShift = view.getUint16(32, true);
this.header.reserved = Array.from(headerBytes.slice(34, 40)).map(b => b.toString(16).padStart(2, '0')).join(' ');
this.header.numDirectorySectors = view.getUint32(40, true);
this.header.numFATSectors = view.getUint32(44, true);
this.header.firstDirectorySector = view.getUint32(48, true);
this.header.transactionSignature = view.getUint32(52, true);
this.header.miniStreamCutoff = view.getUint32(56, true);
this.header.firstMiniFATSector = view.getUint32(60, true);
this.header.numMiniFATSectors = view.getUint32(64, true);
this.header.firstDIFATSector = view.getUint32(68, true);
this.header.numDIFATSectors = view.getUint32(72, true);
this.header.difat = [];
for (let i = 0; i < 109; i++) {
this.header.difat.push(view.getUint32(76 + i * 4, true));
}
this.printProperties();
}
printProperties() {
console.log('Extracted .VSD Properties:');
Object.entries(this.header).forEach(([key, value]) => {
if (Array.isArray(value)) {
console.log(`${key}: [${value.slice(0, 5).map(v => '0x' + v.toString(16)).join(', ')}...] (109 entries)`);
} else {
console.log(`${key}: ${typeof value === 'number' ? '0x' + value.toString(16) : value}`);
}
});
}
writeHeader(newValues = {}) {
Object.assign(this.header, newValues);
const buffer = Buffer.alloc(512);
const signatureBytes = this.header.signature.split(' ').map(hex => parseInt(hex, 16));
buffer.set(signatureBytes, 0);
const clsidBytes = this.header.clsid.split(' ').map(hex => parseInt(hex, 16));
buffer.set(clsidBytes, 8);
const view = new DataView(buffer.buffer);
view.setUint16(24, this.header.minorVersion, true);
view.setUint16(26, this.header.majorVersion, true);
view.setUint16(28, this.header.byteOrder, true);
view.setUint16(30, this.header.sectorShift, true);
view.setUint16(32, this.header.miniSectorShift, true);
const reservedBytes = this.header.reserved.split(' ').map(hex => parseInt(hex, 16));
buffer.set(reservedBytes, 34);
view.setUint32(40, this.header.numDirectorySectors, true);
view.setUint32(44, this.header.numFATSectors, true);
view.setUint32(48, this.header.firstDirectorySector, true);
view.setUint32(52, this.header.transactionSignature, true);
view.setUint32(56, this.header.miniStreamCutoff, true);
view.setUint32(60, this.header.firstMiniFATSector, true);
view.setUint32(64, this.header.numMiniFATSectors, true);
view.setUint32(68, this.header.firstDIFATSector, true);
view.setUint32(72, this.header.numDIFATSectors, true);
for (let i = 0; i < 109; i++) {
view.setUint32(76 + i * 4, this.header.difat[i], true);
}
fs.writeFileSync(this.filepath, buffer, { flag: 'r+' });
}
}
// Example: const handler = new VSDFileHandler('example.vsd');
// To write: handler.writeHeader({ transactionSignature: newValue });
- C Class for .VSD File Handling
The following C code defines a struct-based "class" (using functions) to open a .VSD file, decode the header, read and print the properties to the console, and write the header. Compile with a C compiler (e.g., gcc).
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#define HEADER_SIZE 512
typedef struct {
uint8_t signature[8];
uint8_t clsid[16];
uint16_t minor_version;
uint16_t major_version;
uint16_t byte_order;
uint16_t sector_shift;
uint16_t mini_sector_shift;
uint8_t reserved[6];
uint32_t num_directory_sectors;
uint32_t num_fat_sectors;
uint32_t first_directory_sector;
uint32_t transaction_signature;
uint32_t mini_stream_cutoff;
uint32_t first_mini_fat_sector;
uint32_t num_mini_fat_sectors;
uint32_t first_difat_sector;
uint32_t num_difat_sectors;
uint32_t difat[109];
} VSDHeader;
void read_and_decode(const char* filepath, VSDHeader* header) {
FILE* file = fopen(filepath, "rb");
if (!file) {
perror("Failed to open file");
exit(1);
}
uint8_t buffer[HEADER_SIZE];
if (fread(buffer, 1, HEADER_SIZE, file) < HEADER_SIZE) {
fprintf(stderr, "File too small\n");
fclose(file);
exit(1);
}
fclose(file);
memcpy(header->signature, buffer, 8);
memcpy(header->clsid, buffer + 8, 16);
memcpy(&header->minor_version, buffer + 24, 2);
memcpy(&header->major_version, buffer + 26, 2);
memcpy(&header->byte_order, buffer + 28, 2);
memcpy(&header->sector_shift, buffer + 30, 2);
memcpy(&header->mini_sector_shift, buffer + 32, 2);
memcpy(header->reserved, buffer + 34, 6);
memcpy(&header->num_directory_sectors, buffer + 40, 4);
memcpy(&header->num_fat_sectors, buffer + 44, 4);
memcpy(&header->first_directory_sector, buffer + 48, 4);
memcpy(&header->transaction_signature, buffer + 52, 4);
memcpy(&header->mini_stream_cutoff, buffer + 56, 4);
memcpy(&header->first_mini_fat_sector, buffer + 60, 4);
memcpy(&header->num_mini_fat_sectors, buffer + 64, 4);
memcpy(&header->first_difat_sector, buffer + 68, 4);
memcpy(&header->num_difat_sectors, buffer + 72, 4);
for (int i = 0; i < 109; i++) {
memcpy(&header->difat[i], buffer + 76 + i * 4, 4);
}
print_properties(header);
}
void print_properties(const VSDHeader* header) {
printf("Extracted .VSD Properties:\n");
printf("Header Signature: ");
for (int i = 0; i < 8; i++) printf("%02X ", header->signature[i]);
printf("\n");
printf("Header CLSID: ");
for (int i = 0; i < 16; i++) printf("%02X ", header->clsid[i]);
printf("\n");
printf("Minor Version: 0x%04X\n", header->minor_version);
printf("Major Version: 0x%04X\n", header->major_version);
printf("Byte Order: 0x%04X\n", header->byte_order);
printf("Sector Shift: 0x%04X\n", header->sector_shift);
printf("Mini Sector Shift: 0x%04X\n", header->mini_sector_shift);
printf("Reserved: ");
for (int i = 0; i < 6; i++) printf("%02X ", header->reserved[i]);
printf("\n");
printf("Number of Directory Sectors: %u\n", header->num_directory_sectors);
printf("Number of FAT Sectors: %u\n", header->num_fat_sectors);
printf("First Directory Sector Location: %u\n", header->first_directory_sector);
printf("Transaction Signature Number: %u\n", header->transaction_signature);
printf("Mini Stream Cutoff Size: %u\n", header->mini_stream_cutoff);
printf("First Mini FAT Sector Location: %u\n", header->first_mini_fat_sector);
printf("Number of Mini FAT Sectors: %u\n", header->num_mini_fat_sectors);
printf("First DIFAT Sector Location: %u\n", header->first_difat_sector);
printf("Number of DIFAT Sectors: %u\n", header->num_difat_sectors);
printf("DIFAT Array (sample): ");
for (int i = 0; i < 5; i++) printf("0x%08X ", header->difat[i]);
printf("... (109 entries)\n");
}
void write_header(const char* filepath, const VSDHeader* header) {
FILE* file = fopen(filepath, "r+b");
if (!file) {
perror("Failed to open file for writing");
exit(1);
}
uint8_t buffer[HEADER_SIZE] = {0};
memcpy(buffer, header->signature, 8);
memcpy(buffer + 8, header->clsid, 16);
memcpy(buffer + 24, &header->minor_version, 2);
memcpy(buffer + 26, &header->major_version, 2);
memcpy(buffer + 28, &header->byte_order, 2);
memcpy(buffer + 30, &header->sector_shift, 2);
memcpy(buffer + 32, &header->mini_sector_shift, 2);
memcpy(buffer + 34, header->reserved, 6);
memcpy(buffer + 40, &header->num_directory_sectors, 4);
memcpy(buffer + 44, &header->num_fat_sectors, 4);
memcpy(buffer + 48, &header->first_directory_sector, 4);
memcpy(buffer + 52, &header->transaction_signature, 4);
memcpy(buffer + 56, &header->mini_stream_cutoff, 4);
memcpy(buffer + 60, &header->first_mini_fat_sector, 4);
memcpy(buffer + 64, &header->num_mini_fat_sectors, 4);
memcpy(buffer + 68, &header->first_difat_sector, 4);
memcpy(buffer + 72, &header->num_difat_sectors, 4);
for (int i = 0; i < 109; i++) {
memcpy(buffer + 76 + i * 4, &header->difat[i], 4);
}
fwrite(buffer, 1, HEADER_SIZE, file);
fclose(file);
}
int main(int argc, char** argv) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <filepath.vsd>\n", argv[0]);
return 1;
}
VSDHeader header;
read_and_decode(argv[1], &header);
// To write: modify header, then write_header(argv[1], &header);
return 0;
}