Task 121: .CUR File Format
Task 121: .CUR File Format
1. Properties of the .CUR File Format
The .CUR file format, used for static mouse cursors in Microsoft Windows, is structurally similar to the .ICO format but includes hotspot coordinates for cursor positioning. The properties intrinsic to the format are derived from its binary structure, which consists of a header, directory entries for each image, and image data in BMP format (PNG is not supported for .CUR). All values are in little-endian byte order. Below is a comprehensive list of these properties, organized by section for clarity. These include all fields defined in the format specifications, excluding the raw bitmap data and color tables, which are variable content rather than fixed properties.
Header Properties (Offsets 0-5)
- Reserved: 2 bytes (WORD) – Must be 0.
- Type: 2 bytes (WORD) – Must be 2 for .CUR files.
- Image Count: 2 bytes (WORD) – Number of cursor images in the file.
Directory Entry Properties (Per Image, 16 bytes each, starting at offset 6)
For each image (repeated based on Image Count):
- Width: 1 byte (BYTE) – Image width in pixels (0 represents 256).
- Height: 1 byte (BYTE) – Image height in pixels (0 represents 256).
- Color Count: 1 byte (BYTE) – Number of colors in the palette (0 if no palette or more than 255 colors).
- Reserved (Entry): 1 byte (BYTE) – Must be 0.
- Hotspot X: 2 bytes (WORD) – Horizontal coordinate of the cursor hotspot (pixels from the left).
- Hotspot Y: 2 bytes (WORD) – Vertical coordinate of the cursor hotspot (pixels from the top).
- Image Data Size: 4 bytes (DWORD) – Size of the image data in bytes.
- Image Offset: 4 bytes (DWORD) – Offset from the file start to the image data.
Image Data Properties (BITMAPINFOHEADER, 40 bytes, at Image Offset for each image)
Each image's data begins with a BITMAPINFOHEADER (no BITMAPFILEHEADER is present). These properties apply per image:
- BMP Header Size: 4 bytes (DWORD) – Size of the BITMAPINFOHEADER (typically 40).
- BMP Width: 4 bytes (LONG) – Width of the bitmap in pixels.
- BMP Height: 4 bytes (LONG) – Height of the bitmap in pixels (typically twice the entry Height to include XOR and AND masks).
- BMP Planes: 2 bytes (WORD) – Number of color planes (must be 1).
- BMP Bit Count: 2 bytes (WORD) – Bits per pixel (1, 4, 8, 16, 24, or 32).
- BMP Compression: 4 bytes (DWORD) – Compression method (0 for BI_RGB, 3 for BI_BITFIELDS in 16/32-bit cases).
- BMP Image Size: 4 bytes (DWORD) – Size of the raw bitmap data (0 if uncompressed).
- BMP X Pixels Per Meter: 4 bytes (LONG) – Horizontal resolution in pixels per meter.
- BMP Y Pixels Per Meter: 4 bytes (LONG) – Vertical resolution in pixels per meter.
- BMP Colors Used: 4 bytes (DWORD) – Number of colors used in the palette (0 if all colors are used).
- BMP Colors Important: 4 bytes (DWORD) – Number of important colors (0 if all are important).
These properties define the core structure of the .CUR format, enabling decoding and manipulation of cursor files.
2. Direct Download Links for .CUR Files
Two direct download links for .CUR files are provided below. These are static cursor files suitable for testing or use in Windows environments:
- https://ani.cursors-4u.net/others/oth-9/oth931.cur
- https://ani.cursors-4u.net/others/oth-9/oth930.cur
3. HTML/JavaScript for Drag-and-Drop .CUR File Dumping
The following is a self-contained HTML page with embedded JavaScript that allows a user to drag and drop a .CUR file. Upon dropping, it parses the file and displays all properties listed in section 1 to the screen. This can be embedded in a Ghost blog or used standalone. The code uses the FileReader API to read the file as an ArrayBuffer and unpacks the binary data using DataView.
4. Python Class for .CUR File Handling
The following Python class can open a .CUR file, decode its structure, read and print all properties from section 1 to the console, and write the file back (unchanged or modified). It uses the struct
module for binary unpacking.
import struct
class CurFile:
def __init__(self, filename):
self.filename = filename
self.header = {}
self.entries = []
self.bmp_headers = []
self.data = b'' # Full file data for writing
self._load()
def _load(self):
with open(self.filename, 'rb') as f:
self.data = f.read()
offset = 0
self.header['Reserved'], = struct.unpack_from('<H', self.data, offset); offset += 2
self.header['Type'], = struct.unpack_from('<H', self.data, offset); offset += 2
self.header['Image Count'], = struct.unpack_from('<H', self.data, offset); offset += 2
for i in range(self.header['Image Count']):
entry = {}
entry['Width'], = struct.unpack_from('<B', self.data, offset); offset += 1
entry['Width'] = 256 if entry['Width'] == 0 else entry['Width']
entry['Height'], = struct.unpack_from('<B', self.data, offset); offset += 1
entry['Height'] = 256 if entry['Height'] == 0 else entry['Height']
entry['Color Count'], = struct.unpack_from('<B', self.data, offset); offset += 1
entry['Reserved (Entry)'], = struct.unpack_from('<B', self.data, offset); offset += 1
entry['Hotspot X'], = struct.unpack_from('<H', self.data, offset); offset += 2
entry['Hotspot Y'], = struct.unpack_from('<H', self.data, offset); offset += 2
entry['Image Data Size'], = struct.unpack_from('<I', self.data, offset); offset += 4
entry['Image Offset'], = struct.unpack_from('<I', self.data, offset); offset += 4
self.entries.append(entry)
# BMP Header
bmp_offset = entry['Image Offset']
bmp_header = {}
bmp_header['BMP Header Size'], = struct.unpack_from('<I', self.data, bmp_offset); bmp_offset += 4
bmp_header['BMP Width'], = struct.unpack_from('<i', self.data, bmp_offset); bmp_offset += 4
bmp_header['BMP Height'], = struct.unpack_from('<i', self.data, bmp_offset); bmp_offset += 4
bmp_header['BMP Planes'], = struct.unpack_from('<H', self.data, bmp_offset); bmp_offset += 2
bmp_header['BMP Bit Count'], = struct.unpack_from('<H', self.data, bmp_offset); bmp_offset += 2
bmp_header['BMP Compression'], = struct.unpack_from('<I', self.data, bmp_offset); bmp_offset += 4
bmp_header['BMP Image Size'], = struct.unpack_from('<I', self.data, bmp_offset); bmp_offset += 4
bmp_header['BMP X Pixels Per Meter'], = struct.unpack_from('<i', self.data, bmp_offset); bmp_offset += 4
bmp_header['BMP Y Pixels Per Meter'], = struct.unpack_from('<i', self.data, bmp_offset); bmp_offset += 4
bmp_header['BMP Colors Used'], = struct.unpack_from('<I', self.data, bmp_offset); bmp_offset += 4
bmp_header['BMP Colors Important'], = struct.unpack_from('<I', self.data, bmp_offset)
self.bmp_headers.append(bmp_header)
def print_properties(self):
print("Header:")
for key, value in self.header.items():
print(f"{key}: {value}")
print()
for i, entry in enumerate(self.entries):
print(f"Image {i + 1} Directory Entry:")
for key, value in entry.items():
print(f"{key}: {value}")
print()
print(f"Image {i + 1} BMP Header:")
for key, value in self.bmp_headers[i].items():
print(f"{key}: {value}")
print()
def write(self, output_filename):
# Writes the original data back; modifications can be applied by altering self.data
with open(output_filename, 'wb') as f:
f.write(self.data)
# Example usage:
# cur = CurFile('example.cur')
# cur.print_properties()
# cur.write('output.cur')
5. Java Class for .CUR File Handling
The following Java class can open a .CUR file, decode its structure, read and print all properties from section 1 to the console, and write the file back. It uses RandomAccessFile
for binary I/O.
import java.io.*;
public class CurFile {
private String filename;
private int reserved;
private int type;
private int imageCount;
private Entry[] entries;
private BmpHeader[] bmpHeaders;
private byte[] data; // Full file data for writing
private static class Entry {
int width;
int height;
int colorCount;
int entryReserved;
int hotspotX;
int hotspotY;
int imageDataSize;
int imageOffset;
}
private static class BmpHeader {
int bmpHeaderSize;
int bmpWidth;
int bmpHeight;
int bmpPlanes;
int bmpBitCount;
int bmpCompression;
int bmpImageSize;
int bmpXPelsPerMeter;
int bmpYPelsPerMeter;
int bmpColorsUsed;
int bmpColorsImportant;
}
public CurFile(String filename) throws IOException {
this.filename = filename;
load();
}
private void load() throws IOException {
try (RandomAccessFile raf = new RandomAccessFile(filename, "r")) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = raf.read(buffer)) > -1) {
baos.write(buffer, 0, len);
}
data = baos.toByteArray();
}
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data));
reserved = Short.reverseBytes(dis.readShort());
type = Short.reverseBytes(dis.readShort());
imageCount = Short.reverseBytes(dis.readShort());
entries = new Entry[imageCount];
bmpHeaders = new BmpHeader[imageCount];
for (int i = 0; i < imageCount; i++) {
Entry entry = new Entry();
entry.width = dis.readUnsignedByte();
entry.width = (entry.width == 0) ? 256 : entry.width;
entry.height = dis.readUnsignedByte();
entry.height = (entry.height == 0) ? 256 : entry.height;
entry.colorCount = dis.readUnsignedByte();
entry.entryReserved = dis.readUnsignedByte();
entry.hotspotX = Short.reverseBytes(dis.readShort());
entry.hotspotY = Short.reverseBytes(dis.readShort());
entry.imageDataSize = Integer.reverseBytes(dis.readInt());
entry.imageOffset = Integer.reverseBytes(dis.readInt());
entries[i] = entry;
// BMP Header
DataInputStream bmpDis = new DataInputStream(new ByteArrayInputStream(data, entry.imageOffset, 40));
BmpHeader bmp = new BmpHeader();
bmp.bmpHeaderSize = Integer.reverseBytes(bmpDis.readInt());
bmp.bmpWidth = Integer.reverseBytes(bmpDis.readInt());
bmp.bmpHeight = Integer.reverseBytes(bmpDis.readInt());
bmp.bmpPlanes = Short.reverseBytes(bmpDis.readShort());
bmp.bmpBitCount = Short.reverseBytes(bmpDis.readShort());
bmp.bmpCompression = Integer.reverseBytes(bmpDis.readInt());
bmp.bmpImageSize = Integer.reverseBytes(bmpDis.readInt());
bmp.bmpXPelsPerMeter = Integer.reverseBytes(bmpDis.readInt());
bmp.bmpYPelsPerMeter = Integer.reverseBytes(bmpDis.readInt());
bmp.bmpColorsUsed = Integer.reverseBytes(bmpDis.readInt());
bmp.bmpColorsImportant = Integer.reverseBytes(bmpDis.readInt());
bmpHeaders[i] = bmp;
}
}
public void printProperties() {
System.out.println("Header:");
System.out.println("Reserved: " + reserved);
System.out.println("Type: " + type);
System.out.println("Image Count: " + imageCount);
System.out.println();
for (int i = 0; i < imageCount; i++) {
Entry entry = entries[i];
System.out.println("Image " + (i + 1) + " Directory Entry:");
System.out.println("Width: " + entry.width);
System.out.println("Height: " + entry.height);
System.out.println("Color Count: " + entry.colorCount);
System.out.println("Reserved (Entry): " + entry.entryReserved);
System.out.println("Hotspot X: " + entry.hotspotX);
System.out.println("Hotspot Y: " + entry.hotspotY);
System.out.println("Image Data Size: " + entry.imageDataSize);
System.out.println("Image Offset: " + entry.imageOffset);
System.out.println();
BmpHeader bmp = bmpHeaders[i];
System.out.println("Image " + (i + 1) + " BMP Header:");
System.out.println("BMP Header Size: " + bmp.bmpHeaderSize);
System.out.println("BMP Width: " + bmp.bmpWidth);
System.out.println("BMP Height: " + bmp.bmpHeight);
System.out.println("BMP Planes: " + bmp.bmpPlanes);
System.out.println("BMP Bit Count: " + bmp.bmpBitCount);
System.out.println("BMP Compression: " + bmp.bmpCompression);
System.out.println("BMP Image Size: " + bmp.bmpImageSize);
System.out.println("BMP X Pixels Per Meter: " + bmp.bmpXPelsPerMeter);
System.out.println("BMP Y Pixels Per Meter: " + bmp.bmpYPelsPerMeter);
System.out.println("BMP Colors Used: " + bmp.bmpColorsUsed);
System.out.println("BMP Colors Important: " + bmp.bmpColorsImportant);
System.out.println();
}
}
public void write(String outputFilename) throws IOException {
// Writes the original data; modifications can be applied by altering data array
try (FileOutputStream fos = new FileOutputStream(outputFilename)) {
fos.write(data);
}
}
// Example usage:
// public static void main(String[] args) throws IOException {
// CurFile cur = new CurFile("example.cur");
// cur.printProperties();
// cur.write("output.cur");
// }
}
6. JavaScript Class for .CUR File Handling
The following JavaScript class can open a .CUR file (via File object), decode its structure, read and print all properties from section 1 to the console, and write the file back using Blob and URL. It assumes Node.js or a browser environment with FileReader.
class CurFile {
constructor(file) {
this.file = file;
this.header = {};
this.entries = [];
this.bmpHeaders = [];
this.buffer = null;
}
async load() {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (event) => {
this.buffer = event.target.result;
const view = new DataView(this.buffer);
let offset = 0;
this.header.reserved = view.getUint16(offset, true); offset += 2;
this.header.type = view.getUint16(offset, true); offset += 2;
this.header.imageCount = view.getUint16(offset, true); offset += 2;
for (let i = 0; i < this.header.imageCount; i++) {
const entry = {};
entry.width = view.getUint8(offset); offset += 1;
entry.width = (entry.width === 0) ? 256 : entry.width;
entry.height = view.getUint8(offset); offset += 1;
entry.height = (entry.height === 0) ? 256 : entry.height;
entry.colorCount = view.getUint8(offset); offset += 1;
entry.entryReserved = view.getUint8(offset); offset += 1;
entry.hotspotX = view.getUint16(offset, true); offset += 2;
entry.hotspotY = view.getUint16(offset, true); offset += 2;
entry.imageDataSize = view.getUint32(offset, true); offset += 4;
entry.imageOffset = view.getUint32(offset, true); offset += 4;
this.entries.push(entry);
// BMP Header
let bmpOffset = entry.imageOffset;
const bmp = {};
bmp.bmpHeaderSize = view.getUint32(bmpOffset, true); bmpOffset += 4;
bmp.bmpWidth = view.getInt32(bmpOffset, true); bmpOffset += 4;
bmp.bmpHeight = view.getInt32(bmpOffset, true); bmpOffset += 4;
bmp.bmpPlanes = view.getUint16(bmpOffset, true); bmpOffset += 2;
bmp.bmpBitCount = view.getUint16(bmpOffset, true); bmpOffset += 2;
bmp.bmpCompression = view.getUint32(bmpOffset, true); bmpOffset += 4;
bmp.bmpImageSize = view.getUint32(bmpOffset, true); bmpOffset += 4;
bmp.bmpXPelsPerMeter = view.getInt32(bmpOffset, true); bmpOffset += 4;
bmp.bmpYPelsPerMeter = view.getInt32(bmpOffset, true); bmpOffset += 4;
bmp.bmpColorsUsed = view.getUint32(bmpOffset, true); bmpOffset += 4;
bmp.bmpColorsImportant = view.getUint32(bmpOffset, true);
this.bmpHeaders.push(bmp);
}
resolve();
};
reader.onerror = reject;
reader.readAsArrayBuffer(this.file);
});
}
printProperties() {
console.log('Header:');
console.log(`Reserved: ${this.header.reserved}`);
console.log(`Type: ${this.header.type}`);
console.log(`Image Count: ${this.header.imageCount}`);
console.log('');
this.entries.forEach((entry, i) => {
console.log(`Image ${i + 1} Directory Entry:`);
Object.entries(entry).forEach(([key, value]) => console.log(`${key}: ${value}`));
console.log('');
console.log(`Image ${i + 1} BMP Header:`);
Object.entries(this.bmpHeaders[i]).forEach(([key, value]) => console.log(`${key}: ${value}`));
console.log('');
});
}
write() {
// Creates a downloadable Blob; modifications can be applied to buffer
const blob = new Blob([this.buffer], { type: 'application/octet-stream' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'output.cur';
a.click();
URL.revokeObjectURL(url);
}
}
// Example usage (browser):
// const input = document.querySelector('input[type="file"]');
// input.addEventListener('change', async (e) => {
// const cur = new CurFile(e.target.files[0]);
// await cur.load();
// cur.printProperties();
// cur.write();
// });
7. C++ Class for .CUR File Handling
The following C++ class (using "class" as specified, though C typically uses structs/functions) can open a .CUR file, decode its structure, read and print all properties from section 1 to the console, and write the file back. It uses fstream
for I/O and assumes little-endian host.
#include <iostream>
#include <fstream>
#include <vector>
#include <cstdint>
#include <cstring>
class CurFile {
private:
std::string filename;
uint16_t reserved;
uint16_t type;
uint16_t imageCount;
struct Entry {
uint8_t width;
uint8_t height;
uint8_t colorCount;
uint8_t entryReserved;
uint16_t hotspotX;
uint16_t hotspotY;
uint32_t imageDataSize;
uint32_t imageOffset;
};
struct BmpHeader {
uint32_t bmpHeaderSize;
int32_t bmpWidth;
int32_t bmpHeight;
uint16_t bmpPlanes;
uint16_t bmpBitCount;
uint32_t bmpCompression;
uint32_t bmpImageSize;
int32_t bmpXPelsPerMeter;
int32_t bmpYPelsPerMeter;
uint32_t bmpColorsUsed;
uint32_t bmpColorsImportant;
};
std::vector<Entry> entries;
std::vector<BmpHeader> bmpHeaders;
std::vector<char> data; // Full file data for writing
public:
CurFile(const std::string& filename) : filename(filename) {
load();
}
void load() {
std::ifstream file(filename, std::ios::binary | std::ios::ate);
if (!file) {
std::cerr << "Failed to open file." << std::endl;
return;
}
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
data.resize(size);
file.read(data.data(), size);
size_t offset = 0;
memcpy(&reserved, data.data() + offset, sizeof(reserved)); offset += 2;
memcpy(&type, data.data() + offset, sizeof(type)); offset += 2;
memcpy(&imageCount, data.data() + offset, sizeof(imageCount)); offset += 2;
entries.resize(imageCount);
bmpHeaders.resize(imageCount);
for (uint16_t i = 0; i < imageCount; ++i) {
Entry& entry = entries[i];
memcpy(&entry.width, data.data() + offset, 1); offset += 1;
entry.width = (entry.width == 0) ? 256 : entry.width;
memcpy(&entry.height, data.data() + offset, 1); offset += 1;
entry.height = (entry.height == 0) ? 256 : entry.height;
memcpy(&entry.colorCount, data.data() + offset, 1); offset += 1;
memcpy(&entry.entryReserved, data.data() + offset, 1); offset += 1;
memcpy(&entry.hotspotX, data.data() + offset, sizeof(entry.hotspotX)); offset += 2;
memcpy(&entry.hotspotY, data.data() + offset, sizeof(entry.hotspotY)); offset += 2;
memcpy(&entry.imageDataSize, data.data() + offset, sizeof(entry.imageDataSize)); offset += 4;
memcpy(&entry.imageOffset, data.data() + offset, sizeof(entry.imageOffset)); offset += 4;
// BMP Header
size_t bmpOffset = entry.imageOffset;
BmpHeader& bmp = bmpHeaders[i];
memcpy(&bmp.bmpHeaderSize, data.data() + bmpOffset, sizeof(bmp.bmpHeaderSize)); bmpOffset += 4;
memcpy(&bmp.bmpWidth, data.data() + bmpOffset, sizeof(bmp.bmpWidth)); bmpOffset += 4;
memcpy(&bmp.bmpHeight, data.data() + bmpOffset, sizeof(bmp.bmpHeight)); bmpOffset += 4;
memcpy(&bmp.bmpPlanes, data.data() + bmpOffset, sizeof(bmp.bmpPlanes)); bmpOffset += 2;
memcpy(&bmp.bmpBitCount, data.data() + bmpOffset, sizeof(bmp.bmpBitCount)); bmpOffset += 2;
memcpy(&bmp.bmpCompression, data.data() + bmpOffset, sizeof(bmp.bmpCompression)); bmpOffset += 4;
memcpy(&bmp.bmpImageSize, data.data() + bmpOffset, sizeof(bmp.bmpImageSize)); bmpOffset += 4;
memcpy(&bmp.bmpXPelsPerMeter, data.data() + bmpOffset, sizeof(bmp.bmpXPelsPerMeter)); bmpOffset += 4;
memcpy(&bmp.bmpYPelsPerMeter, data.data() + bmpOffset, sizeof(bmp.bmpYPelsPerMeter)); bmpOffset += 4;
memcpy(&bmp.bmpColorsUsed, data.data() + bmpOffset, sizeof(bmp.bmpColorsUsed)); bmpOffset += 4;
memcpy(&bmp.bmpColorsImportant, data.data() + bmpOffset, sizeof(bmp.bmpColorsImportant));
}
}
void printProperties() const {
std::cout << "Header:" << std::endl;
std::cout << "Reserved: " << reserved << std::endl;
std::cout << "Type: " << type << std::endl;
std::cout << "Image Count: " << imageCount << std::endl;
std::cout << std::endl;
for (uint16_t i = 0; i < imageCount; ++i) {
const Entry& entry = entries[i];
std::cout << "Image " << (i + 1) << " Directory Entry:" << std::endl;
std::cout << "Width: " << static_cast<int>(entry.width) << std::endl;
std::cout << "Height: " << static_cast<int>(entry.height) << std::endl;
std::cout << "Color Count: " << static_cast<int>(entry.colorCount) << std::endl;
std::cout << "Reserved (Entry): " << static_cast<int>(entry.entryReserved) << std::endl;
std::cout << "Hotspot X: " << entry.hotspotX << std::endl;
std::cout << "Hotspot Y: " << entry.hotspotY << std::endl;
std::cout << "Image Data Size: " << entry.imageDataSize << std::endl;
std::cout << "Image Offset: " << entry.imageOffset << std::endl;
std::cout << std::endl;
const BmpHeader& bmp = bmpHeaders[i];
std::cout << "Image " << (i + 1) << " BMP Header:" << std::endl;
std::cout << "BMP Header Size: " << bmp.bmpHeaderSize << std::endl;
std::cout << "BMP Width: " << bmp.bmpWidth << std::endl;
std::cout << "BMP Height: " << bmp.bmpHeight << std::endl;
std::cout << "BMP Planes: " << bmp.bmpPlanes << std::endl;
std::cout << "BMP Bit Count: " << bmp.bmpBitCount << std::endl;
std::cout << "BMP Compression: " << bmp.bmpCompression << std::endl;
std::cout << "BMP Image Size: " << bmp.bmpImageSize << std::endl;
std::cout << "BMP X Pixels Per Meter: " << bmp.bmpXPelsPerMeter << std::endl;
std::cout << "BMP Y Pixels Per Meter: " << bmp.bmpYPelsPerMeter << std::endl;
std::cout << "BMP Colors Used: " << bmp.bmpColorsUsed << std::endl;
std::cout << "BMP Colors Important: " << bmp.bmpColorsImportant << std::endl;
std::cout << std::endl;
}
}
void write(const std::string& outputFilename) const {
// Writes the original data; modifications can be applied to data vector
std::ofstream outFile(outputFilename, std::ios::binary);
if (outFile) {
outFile.write(data.data(), data.size());
}
}
};
// Example usage:
// int main() {
// CurFile cur("example.cur");
// cur.printProperties();
// cur.write("output.cur");
// return 0;
// }