Task 432: .MSSTYLES File Format
Task 432: .MSSTYLES File Format
.MSSTYLES File Format Specifications
The .msstyles file format is used for Windows visual styles (themes), introduced in Windows XP. It is essentially a Portable Executable (PE) file (DLL-like structure without code sections) that contains resources defining the visual elements like images, colors, and styles for UI components. Microsoft does not officially document or support authoring these files, so specifications are based on reverse engineering. The format includes specific resource types, with the main style properties stored in binary format within the VARIANT resource. For Windows 8 and later, additional resources were added. Detailed structure includes property headers and data unions for various types like integers, colors, rectangles, etc.
The full reverse-engineered specification (from community sources) is as follows:
- Overall Structure: Regular PE file with no .text section, only resources.
- Resource Types:
- AMAP: Animation Map - Contains animation info for STREAM resources.
- BCMAP: Base Class Map - Information about class inheritance.
- CMAP: Class Map - All class names in sequential order as UTF-16 strings.
- PACKTHEM_VERSION: Theme format version (e.g., 03 00 for WinXP, 04 00 for newer).
- RMAP: Root Map - Global properties in binary format.
- IMAGE: Image resources (bitmaps, etc.).
- STREAM: Image atlas resources for animations.
- VARIANT: Contains all properties in binary format, organized per variant from VMAP. This is the core where style properties are defined.
- VMAP: Variant Map - List of style variants in the file.
- Win8+ Resources:
- DESKTOP: Points to specific class/part IDs (e.g., TaskbarShowDesktop).
- PVL: Related to timing functions or animations.
- IMMERSIVE: Related to timing functions or animations.
- VARIANT Resource Format: A list of properties, each with a 32-byte header followed by data and padding (to multiple of 8 bytes). Properties are sorted by classId, partId, stateId, nameId.
- Property Header (32 bytes):
- int32_t nameID: Property name ID (from MSDN theme properties).
- int32_t typeID: Property type ID.
- int32_t classID: Index to class in CMAP.
- int32_t partID: Part ID (from vsstyle.h).
- int32_t stateID: State ID (from vsstyle.h).
- int32_t shortFlag: If non-zero, no data follows; field may contain data.
- int32_t reserved: Always 0.
- int32_t sizeInBytes: Size of following data (excludes padding).
- Property Data: Union based on typeID, e.g.:
- Int: int32_t value.
- Size: int32_t size.
- Bool: int32_t boolvalue.
- Color: unsigned char r,g,b,a.
- Rect/Margin: int32_t left,top,right,bottom.
- Enum: int32_t enumvalue.
- Position: int32_t x,y.
- IntList: int32_t numInts, firstInt...
- ColorList: int32_t firstColorBGR...
- Text: wchar_t firstChar... (UTF-16 string).
To fully decode, parse the PE resource directory to extract these, then parse VARIANT for detailed properties.
- List of all properties of this file format intrinsic to its file system:
- AMAP (Animation Map): Animation information for STREAM resources.
- BCMAP (Base Class Map): Class inheritance information.
- CMAP (Class Map): Sequential UTF-16 class names.
- PACKTHEM_VERSION: Theme version indicator.
- RMAP (Root Map): Global binary properties.
- IMAGE: Image resources.
- STREAM: Animation image atlases.
- VARIANT: Binary style properties per variant.
- VMAP (Variant Map): List of variants.
- DESKTOP (Win8+): Pointer to desktop-related classes/parts.
- PVL (Win8+): Animation/timing functions.
- IMMERSIVE (Win8+): Animation/timing functions.
These are the core resource types ("properties") defining the format's structure.
Two direct download links for .msstyles files:
- https://gist.githubusercontent.com/rikka0w0/971e247871154427d5e62c7b3a7ac1d4/raw/ba66de971dfcc538a5e820e3f143dc3f71846029/colored.msstyles
- https://gist.githubusercontent.com/rikka0w0/971e247871154427d5e62c7b3a7ac1d4/raw/ba66de971dfcc538a5e820e3f143dc3f71846029/colored_aerolite.msstyles
Ghost blog embedded HTML JavaScript for drag-and-drop .msstyles file dumper:
This HTML/JS can be embedded in a Ghost blog post. It allows dragging a .msstyles file, parses the PE structure to find root-level named resources matching the property list, and dumps them to the screen.
- Python class for .msstyles handling:
import struct
import os
class MSStylesHandler:
def __init__(self, filepath):
self.filepath = filepath
self.data = None
self.properties = ['AMAP', 'BCMAP', 'CMAP', 'PACKTHEM_VERSION', 'RMAP', 'IMAGE', 'STREAM', 'VARIANT', 'VMAP', 'DESKTOP', 'PVL', 'IMMERSIVE']
self.found_props = []
def read_decode(self):
with open(self.filepath, 'rb') as f:
self.data = f.read()
# Parse PE
if self.data[0:2] != b'MZ':
raise ValueError('Not a valid PE file')
pe_offset = struct.unpack('<I', self.data[0x3C:0x40])[0]
if self.data[pe_offset:pe_offset+4] != b'PE\0\0':
raise ValueError('Not a valid PE file')
opt_header_offset = pe_offset + 24
num_data_dirs = struct.unpack('<I', self.data[opt_header_offset + 92:opt_header_offset + 96])[0]
if num_data_dirs < 3:
raise ValueError('No resource directory')
resource_rva = struct.unpack('<I', self.data[opt_header_offset + 96 + 8:opt_header_offset + 96 + 12])[0]
resource_size = struct.unpack('<I', self.data[opt_header_offset + 96 + 12:opt_header_offset + 96 + 16])[0]
num_sections = struct.unpack('<H', self.data[pe_offset + 6:pe_offset + 8])[0]
section_offset = opt_header_offset + struct.unpack('<H', self.data[opt_header_offset - 4:opt_header_offset - 2])[0]
resource_offset = 0
for i in range(num_sections):
sec_va = struct.unpack('<I', self.data[section_offset + 12:section_offset + 16])[0]
sec_size = struct.unpack('<I', self.data[section_offset + 8:section_offset + 12])[0]
sec_raw_ptr = struct.unpack('<I', self.data[section_offset + 20:section_offset + 24])[0]
if resource_rva >= sec_va and resource_rva < sec_va + sec_size:
resource_offset = sec_raw_ptr + (resource_rva - sec_va)
break
section_offset += 40
if not resource_offset:
raise ValueError('Resource section not found')
num_named = struct.unpack('<H', self.data[resource_offset + 12:resource_offset + 14])[0]
num_id = struct.unpack('<H', self.data[resource_offset + 14:resource_offset + 16])[0]
entry_offset = resource_offset + 16
for i in range(num_named + num_id):
name_rva = struct.unpack('<I', self.data[entry_offset:entry_offset + 4])[0]
if name_rva & 0x80000000:
name_offset = resource_offset + (name_rva & 0x7FFFFFFF)
name_len = struct.unpack('<H', self.data[name_offset:name_offset + 2])[0]
name = self.data[name_offset + 2:name_offset + 2 + name_len * 2].decode('utf-16le')
if name in self.properties:
self.found_props.append(name)
entry_offset += 8
def print_properties(self):
if not self.found_props:
print('No properties found or file not read.')
else:
print('Found properties:')
for prop in self.found_props:
print(prop)
def write(self, new_filepath):
# For write, save the original data (dummy modify: append a byte, but to keep valid, just copy)
# Real write would modify resources, but here simple copy as example
with open(new_filepath, 'wb') as f:
f.write(self.data)
print(f'File written to {new_filepath}')
# Example usage:
# handler = MSStylesHandler('example.msstyles')
# handler.read_decode()
# handler.print_properties()
# handler.write('modified.msstyles')
This class opens the file, decodes the PE to find matching resources (properties), prints them to console, and writes a copy of the file (write can be extended for modifications).
- Java class for .msstyles handling:
import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
import java.nio.file.*;
public class MSStylesHandler {
private String filepath;
private byte[] data;
private String[] properties = {"AMAP", "BCMAP", "CMAP", "PACKTHEM_VERSION", "RMAP", "IMAGE", "STREAM", "VARIANT", "VMAP", "DESKTOP", "PVL", "IMMERSIVE"};
private java.util.List<String> foundProps = new java.util.ArrayList<>();
public MSStylesHandler(String filepath) {
this.filepath = filepath;
}
public void readDecode() throws IOException {
data = Files.readAllBytes(Paths.get(filepath));
ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
if (buffer.getShort(0) != 0x5A4D) throw new IOException("Not a valid PE file");
int peOffset = buffer.getInt(0x3C);
if (buffer.getInt(peOffset) != 0x4550) throw new IOException("Not a valid PE file");
int optHeaderOffset = peOffset + 24;
int numDataDirs = buffer.getInt(optHeaderOffset + 92);
if (numDataDirs < 3) throw new IOException("No resource directory");
int resourceRVA = buffer.getInt(optHeaderOffset + 96 + 8);
int resourceSize = buffer.getInt(optHeaderOffset + 96 + 12);
short numSections = buffer.getShort(peOffset + 6);
int sectionOffset = optHeaderOffset + buffer.getShort(optHeaderOffset - 4);
int resourceOffset = 0;
for (int i = 0; i < numSections; i++) {
int secVA = buffer.getInt(sectionOffset + 12);
int secSize = buffer.getInt(sectionOffset + 8);
int secRawPtr = buffer.getInt(sectionOffset + 20);
if (resourceRVA >= secVA && resourceRVA < secVA + secSize) {
resourceOffset = secRawPtr + (resourceRVA - secVA);
break;
}
sectionOffset += 40;
}
if (resourceOffset == 0) throw new IOException("Resource section not found");
short numNamed = buffer.getShort(resourceOffset + 12);
short numId = buffer.getShort(resourceOffset + 14);
int entryOffset = resourceOffset + 16;
for (int i = 0; i < numNamed + numId; i++) {
int nameRVA = buffer.getInt(entryOffset);
if ((nameRVA & 0x80000000) != 0) {
int nameOffset = resourceOffset + (nameRVA & 0x7FFFFFFF);
short nameLen = buffer.getShort(nameOffset);
StringBuilder name = new StringBuilder();
for (int j = 0; j < nameLen; j++) {
name.append((char) buffer.getShort(nameOffset + 2 + j * 2));
}
String propName = name.toString();
for (String prop : properties) {
if (prop.equals(propName)) {
foundProps.add(propName);
break;
}
}
}
entryOffset += 8;
}
}
public void printProperties() {
if (foundProps.isEmpty()) {
System.out.println("No properties found or file not read.");
} else {
System.out.println("Found properties:");
for (String prop : foundProps) {
System.out.println(prop);
}
}
}
public void write(String newFilepath) throws IOException {
// Simple copy for write (extend for mods)
Files.write(Paths.get(newFilepath), data);
System.out.println("File written to " + newFilepath);
}
// Example usage:
// public static void main(String[] args) throws IOException {
// MSStylesHandler handler = new MSStylesHandler("example.msstyles");
// handler.readDecode();
// handler.printProperties();
// handler.write("modified.msstyles");
// }
}
This Java class performs similar operations: reads/decodes the file, prints found properties to console, and writes a copy.
- JavaScript class for .msstyles handling:
const fs = require('fs'); // For Node.js
class MSStylesHandler {
constructor(filepath) {
this.filepath = filepath;
this.data = null;
this.properties = ['AMAP', 'BCMAP', 'CMAP', 'PACKTHEM_VERSION', 'RMAP', 'IMAGE', 'STREAM', 'VARIANT', 'VMAP', 'DESKTOP', 'PVL', 'IMMERSIVE'];
this.foundProps = [];
}
readDecode() {
this.data = fs.readFileSync(this.filepath);
const view = new DataView(this.data.buffer);
if (view.getUint16(0, true) !== 0x5A4D) throw new Error('Not a valid PE file');
const peOffset = view.getUint32(0x3C, true);
if (view.getUint32(peOffset, true) !== 0x4550) throw new Error('Not a valid PE file');
const optHeaderOffset = peOffset + 24;
const numDataDirs = view.getUint32(optHeaderOffset + 92, true);
if (numDataDirs < 3) throw new Error('No resource directory');
const resourceRVA = view.getUint32(optHeaderOffset + 96 + 8, true);
const resourceSize = view.getUint32(optHeaderOffset + 96 + 12, true);
const numSections = view.getUint16(peOffset + 6, true);
let sectionOffset = optHeaderOffset + view.getUint16(optHeaderOffset - 4, true);
let resourceOffset = 0;
for (let i = 0; i < numSections; i++) {
const secVA = view.getUint32(sectionOffset + 12, true);
const secSize = view.getUint32(sectionOffset + 8, true);
const secRawPtr = view.getUint32(sectionOffset + 20, true);
if (resourceRVA >= secVA && resourceRVA < secVA + secSize) {
resourceOffset = secRawPtr + (resourceRVA - secVA);
break;
}
sectionOffset += 40;
}
if (!resourceOffset) throw new Error('Resource section not found');
const numNamed = view.getUint16(resourceOffset + 12, true);
const numId = view.getUint16(resourceOffset + 14, true);
let entryOffset = resourceOffset + 16;
for (let i = 0; i < numNamed + numId; i++) {
const nameRVA = view.getUint32(entryOffset, true);
if (nameRVA & 0x80000000) {
const nameOffset = resourceOffset + (nameRVA & 0x7FFFFFFF);
const nameLen = view.getUint16(nameOffset, true);
let name = '';
for (let j = 0; j < nameLen; j++) {
name += String.fromCharCode(view.getUint16(nameOffset + 2 + j * 2, true));
}
if (this.properties.includes(name)) this.foundProps.push(name);
}
entryOffset += 8;
}
}
printProperties() {
if (this.foundProps.length === 0) {
console.log('No properties found or file not read.');
} else {
console.log('Found properties:');
this.foundProps.forEach(prop => console.log(prop));
}
}
write(newFilepath) {
// Simple copy
fs.writeFileSync(newFilepath, this.data);
console.log(`File written to ${newFilepath}`);
}
}
// Example usage:
// const handler = new MSStylesHandler('example.msstyles');
// handler.readDecode();
// handler.printProperties();
// handler.write('modified.msstyles');
This JS class (for Node.js) reads/decodes, prints to console, and writes a copy.
- C++ class for .msstyles handling:
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <cstdint>
class MSStylesHandler {
private:
std::string filepath;
std::vector<uint8_t> data;
std::vector<std::string> properties = {"AMAP", "BCMAP", "CMAP", "PACKTHEM_VERSION", "RMAP", "IMAGE", "STREAM", "VARIANT", "VMAP", "DESKTOP", "PVL", "IMMERSIVE"};
std::vector<std::string> foundProps;
uint32_t readUint32(size_t offset) const {
return (data[offset + 3] << 24) | (data[offset + 2] << 16) | (data[offset + 1] << 8) | data[offset];
}
uint16_t readUint16(size_t offset) const {
return (data[offset + 1] << 8) | data[offset];
}
public:
MSStylesHandler(const std::string& fp) : filepath(fp) {}
void readDecode() {
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
if (!file) throw std::runtime_error("Cannot open file");
size_t size = file.tellg();
data.resize(size);
file.seekg(0);
file.read(reinterpret_cast<char*>(data.data()), size);
if (readUint16(0) != 0x5A4D) throw std::runtime_error("Not a valid PE file");
uint32_t peOffset = readUint32(0x3C);
if (readUint32(peOffset) != 0x4550) throw std::runtime_error("Not a valid PE file");
uint32_t optHeaderOffset = peOffset + 24;
uint32_t numDataDirs = readUint32(optHeaderOffset + 92);
if (numDataDirs < 3) throw std::runtime_error("No resource directory");
uint32_t resourceRVA = readUint32(optHeaderOffset + 96 + 8);
uint32_t resourceSize = readUint32(optHeaderOffset + 96 + 12);
uint16_t numSections = readUint16(peOffset + 6);
uint32_t sectionOffset = optHeaderOffset + readUint16(optHeaderOffset - 4);
uint32_t resourceOffset = 0;
for (uint16_t i = 0; i < numSections; ++i) {
uint32_t secVA = readUint32(sectionOffset + 12);
uint32_t secSize = readUint32(sectionOffset + 8);
uint32_t secRawPtr = readUint32(sectionOffset + 20);
if (resourceRVA >= secVA && resourceRVA < secVA + secSize) {
resourceOffset = secRawPtr + (resourceRVA - secVA);
break;
}
sectionOffset += 40;
}
if (resourceOffset == 0) throw std::runtime_error("Resource section not found");
uint16_t numNamed = readUint16(resourceOffset + 12);
uint16_t numId = readUint16(resourceOffset + 14);
uint32_t entryOffset = resourceOffset + 16;
for (uint16_t i = 0; i < numNamed + numId; ++i) {
uint32_t nameRVA = readUint32(entryOffset);
if (nameRVA & 0x80000000) {
uint32_t nameOffset = resourceOffset + (nameRVA & 0x7FFFFFFF);
uint16_t nameLen = readUint16(nameOffset);
std::string name;
for (uint16_t j = 0; j < nameLen; ++j) {
name += static_cast<char>(readUint16(nameOffset + 2 + j * 2));
}
for (const auto& prop : properties) {
if (prop == name) {
foundProps.push_back(name);
break;
}
}
}
entryOffset += 8;
}
}
void printProperties() const {
if (foundProps.empty()) {
std::cout << "No properties found or file not read." << std::endl;
} else {
std::cout << "Found properties:" << std::endl;
for (const auto& prop : foundProps) {
std::cout << prop << std::endl;
}
}
}
void write(const std::string& newFilepath) const {
std::ofstream out(newFilepath, std::ios::binary);
if (!out) throw std::runtime_error("Cannot write file");
out.write(reinterpret_cast<const char*>(data.data()), data.size());
std::cout << "File written to " << newFilepath << std::endl;
}
};
// Example usage:
// int main() {
// try {
// MSStylesHandler handler("example.msstyles");
// handler.readDecode();
// handler.printProperties();
// handler.write("modified.msstyles");
// } catch (const std::exception& e) {
// std::cerr << "Error: " << e.what() << std::endl;
// }
// return 0;
// }
This C++ class opens the file, decodes the PE to find and print properties to console, and writes a copy of the file.