Task 833: .XLS File Format
Task 833: .XLS File Format
.XLS File Format Specifications
The .XLS file format (Excel Binary File Format, or BIFF) is a proprietary binary format used by Microsoft Excel versions 97 through 2003. It is built on the Compound File Binary Format (CFB), also known as OLE Structured Storage, which organizes data in a file-system-like structure with storages and streams. The file begins with a fixed 512-byte header that defines key structural elements. The primary content is in a stream named "Workbook" (or "Book" in older versions), which contains BIFF records for spreadsheet data. The format supports little-endian byte order and includes metadata in property set streams like "\005SummaryInformation" and "\005DocumentSummaryInformation".
- List of all the properties of this file format intrinsic to its file system:
These are the fields from the CFB header, which define the file's internal "file system" structure:
- Signature: 8 bytes, fixed value 0xD0 CF 11 E0 A1 B1 1A E1 (magic number identifying CFB files).
- CLSID: 16 bytes, typically all zeros (0x00000000-0000-0000-0000-000000000000).
- Minor Version: 2 bytes (unsigned short), usually 0x003E.
- Major Version: 2 bytes (unsigned short), 0x0003 (version 3, sector size 512 bytes) or 0x0004 (version 4, sector size 4096 bytes).
- Byte Order: 2 bytes (unsigned short), fixed 0xFFFE (indicating little-endian).
- Sector Shift: 2 bytes (unsigned short), 9 (for 512-byte sectors in v3) or 12 (for 4096-byte sectors in v4).
- Mini Sector Shift: 2 bytes (unsigned short), fixed 6 (for 64-byte mini-sectors).
- Reserved: 2 bytes, must be 0.
- Reserved1: 4 bytes (unsigned long), must be 0.
- Number of Directory Sectors: 4 bytes (unsigned long), 0 for major version 3.
- Number of FAT Sectors: 4 bytes (unsigned long), number of sectors in the FAT chain.
- First Directory Sector Location: 4 bytes (unsigned long), starting sector ID for the directory stream.
- Transaction Signature Number: 4 bytes (unsigned long), for file versioning/transactions.
- Mini Stream Cutoff Size: 4 bytes (unsigned long), fixed 0x00001000 (4096 bytes).
- First Mini FAT Sector Location: 4 bytes (unsigned long), starting sector ID for mini FAT.
- Number of Mini FAT Sectors: 4 bytes (unsigned long), count of mini FAT sectors.
- First DIFAT Sector Location: 4 bytes (unsigned long), starting sector ID for DIFAT (or 0xFFFFFFFE if none).
- Number of DIFAT Sectors: 4 bytes (unsigned long), count of DIFAT sectors.
- DIFAT: 436 bytes (109 unsigned longs), array of FAT sector locations (first 109 entries; padded with 0xFFFFFFFF if unused).
- Two direct download links for .XLS files:
- https://file-examples.com/wp-content/storage/2017/02/file_example_XLS_10.xls
- https://file-examples.com/wp-content/storage/2017/02/file_example_XLS_50.xls
- Ghost blog embedded HTML/JavaScript for drag-and-drop .XLS file to dump properties:
Here's an embeddable HTML snippet with JavaScript that can be placed in a Ghost blog post (or any HTML-enabled blog). It creates a drop zone; when a .XLS file is dropped, it reads the first 512 bytes, parses the header properties, and displays them on the screen.
- Python class for .XLS handling:
import struct
import os
class XLSHeaderParser:
def __init__(self, filename):
self.filename = filename
self.properties = {}
self._read_header()
def _read_header(self):
with open(self.filename, 'rb') as f:
header = f.read(512)
if len(header) < 512:
raise ValueError("File too small to be a valid .XLS")
# Unpack properties
self.properties['Signature'] = ' '.join(f'0x{byte:02X}' for byte in header[0:8])
self.properties['CLSID'] = ' '.join(f'0x{byte:02X}' for byte in header[8:24])
self.properties['Minor Version'] = f'0x{struct.unpack_from("<H", header, 24)[0]:04X}'
self.properties['Major Version'] = f'0x{struct.unpack_from("<H", header, 26)[0]:04X}'
self.properties['Byte Order'] = f'0x{struct.unpack_from("<H", header, 28)[0]:04X}'
self.properties['Sector Shift'] = struct.unpack_from("<H", header, 30)[0]
self.properties['Mini Sector Shift'] = struct.unpack_from("<H", header, 32)[0]
self.properties['Reserved'] = f'0x{struct.unpack_from("<H", header, 34)[0]:04X}'
self.properties['Reserved1'] = f'0x{struct.unpack_from("<I", header, 36)[0]:08X}'
self.properties['Number of Directory Sectors'] = struct.unpack_from("<I", header, 40)[0]
self.properties['Number of FAT Sectors'] = struct.unpack_from("<I", header, 44)[0]
self.properties['First Directory Sector Location'] = f'0x{struct.unpack_from("<I", header, 48)[0]:08X}'
self.properties['Transaction Signature Number'] = struct.unpack_from("<I", header, 52)[0]
self.properties['Mini Stream Cutoff Size'] = struct.unpack_from("<I", header, 56)[0]
self.properties['First Mini FAT Sector Location'] = f'0x{struct.unpack_from("<I", header, 60)[0]:08X}'
self.properties['Number of Mini FAT Sectors'] = struct.unpack_from("<I", header, 64)[0]
self.properties['First DIFAT Sector Location'] = f'0x{struct.unpack_from("<I", header, 68)[0]:08X}'
self.properties['Number of DIFAT Sectors'] = struct.unpack_from("<I", header, 72)[0]
# DIFAT: 109 entries
difat = []
for i in range(109):
val = struct.unpack_from("<I", header, 76 + i * 4)[0]
difat.append(f'0x{val:08X}')
self.properties['DIFAT'] = ' '.join(difat)
def print_properties(self):
for key, value in self.properties.items():
print(f"{key}: {value}")
def write_header(self, new_filename=None):
# Reads original file, replaces header with current properties, writes to new file
if not new_filename:
new_filename = self.filename + '.modified'
with open(self.filename, 'rb') as f_in:
data = f_in.read()
header = self._pack_header()
with open(new_filename, 'wb') as f_out:
f_out.write(header + data[512:])
print(f"Modified file written to {new_filename}")
def _pack_header(self):
header = bytearray(512)
# Signature
sig_bytes = bytes.fromhex(self.properties['Signature'].replace('0x', ' '))
header[0:8] = sig_bytes
# CLSID
clsid_bytes = bytes.fromhex(self.properties['CLSID'].replace('0x', ' '))
header[8:24] = clsid_bytes
# Minor Version
struct.pack_into("<H", header, 24, int(self.properties['Minor Version'], 16))
# Major Version
struct.pack_into("<H", header, 26, int(self.properties['Major Version'], 16))
# Byte Order
struct.pack_into("<H", header, 28, int(self.properties['Byte Order'], 16))
# Sector Shift
struct.pack_into("<H", header, 30, self.properties['Sector Shift'])
# Mini Sector Shift
struct.pack_into("<H", header, 32, self.properties['Mini Sector Shift'])
# Reserved
struct.pack_into("<H", header, 34, int(self.properties['Reserved'], 16))
# Reserved1
struct.pack_into("<I", header, 36, int(self.properties['Reserved1'], 16))
# Number of Directory Sectors
struct.pack_into("<I", header, 40, self.properties['Number of Directory Sectors'])
# Number of FAT Sectors
struct.pack_into("<I", header, 44, self.properties['Number of FAT Sectors'])
# First Directory Sector Location
struct.pack_into("<I", header, 48, int(self.properties['First Directory Sector Location'], 16))
# Transaction Signature Number
struct.pack_into("<I", header, 52, self.properties['Transaction Signature Number'])
# Mini Stream Cutoff Size
struct.pack_into("<I", header, 56, self.properties['Mini Stream Cutoff Size'])
# First Mini FAT Sector Location
struct.pack_into("<I", header, 60, int(self.properties['First Mini FAT Sector Location'], 16))
# Number of Mini FAT Sectors
struct.pack_into("<I", header, 64, self.properties['Number of Mini FAT Sectors'])
# First DIFAT Sector Location
struct.pack_into("<I", header, 68, int(self.properties['First DIFAT Sector Location'], 16))
# Number of DIFAT Sectors
struct.pack_into("<I", header, 72, self.properties['Number of DIFAT Sectors'])
# DIFAT
difat_vals = self.properties['DIFAT'].split()
for i in range(109):
struct.pack_into("<I", header, 76 + i * 4, int(difat_vals[i], 16))
return header
# Example usage:
# parser = XLSHeaderParser('example.xls')
# parser.print_properties()
# parser.write_header()
- Java class for .XLS handling:
import java.io.*;
import java.nio.*;
import java.util.*;
public class XLSHeaderParser {
private String filename;
private Map<String, Object> properties = new HashMap<>();
public XLSHeaderParser(String filename) throws IOException {
this.filename = filename;
readHeader();
}
private void readHeader() throws IOException {
try (RandomAccessFile raf = new RandomAccessFile(filename, "r")) {
byte[] header = new byte[512];
if (raf.read(header) < 512) {
throw new IOException("File too small to be a valid .XLS");
}
ByteBuffer bb = ByteBuffer.wrap(header).order(ByteOrder.LITTLE_ENDIAN);
// Signature
StringBuilder sig = new StringBuilder();
for (int i = 0; i < 8; i++) {
sig.append(String.format("0x%02X ", bb.get(i) & 0xFF));
}
properties.put("Signature", sig.toString().trim());
// CLSID
StringBuilder clsid = new StringBuilder();
for (int i = 8; i < 24; i++) {
clsid.append(String.format("0x%02X ", bb.get(i) & 0xFF));
}
properties.put("CLSID", clsid.toString().trim());
// Minor Version
properties.put("Minor Version", String.format("0x%04X", bb.getShort(24) & 0xFFFF));
// Major Version
properties.put("Major Version", String.format("0x%04X", bb.getShort(26) & 0xFFFF));
// Byte Order
properties.put("Byte Order", String.format("0x%04X", bb.getShort(28) & 0xFFFF));
// Sector Shift
properties.put("Sector Shift", bb.getShort(30) & 0xFFFF);
// Mini Sector Shift
properties.put("Mini Sector Shift", bb.getShort(32) & 0xFFFF);
// Reserved
properties.put("Reserved", String.format("0x%04X", bb.getShort(34) & 0xFFFF));
// Reserved1
properties.put("Reserved1", String.format("0x%08X", bb.getInt(36)));
// Number of Directory Sectors
properties.put("Number of Directory Sectors", bb.getInt(40));
// Number of FAT Sectors
properties.put("Number of FAT Sectors", bb.getInt(44));
// First Directory Sector Location
properties.put("First Directory Sector Location", String.format("0x%08X", bb.getInt(48)));
// Transaction Signature Number
properties.put("Transaction Signature Number", bb.getInt(52));
// Mini Stream Cutoff Size
properties.put("Mini Stream Cutoff Size", bb.getInt(56));
// First Mini FAT Sector Location
properties.put("First Mini FAT Sector Location", String.format("0x%08X", bb.getInt(60)));
// Number of Mini FAT Sectors
properties.put("Number of Mini FAT Sectors", bb.getInt(64));
// First DIFAT Sector Location
properties.put("First DIFAT Sector Location", String.format("0x%08X", bb.getInt(68)));
// Number of DIFAT Sectors
properties.put("Number of DIFAT Sectors", bb.getInt(72));
// DIFAT
StringBuilder difat = new StringBuilder();
for (int i = 0; i < 109; i++) {
difat.append(String.format("0x%08X ", bb.getInt(76 + i * 4)));
}
properties.put("DIFAT", difat.toString().trim());
}
}
public void printProperties() {
properties.forEach((key, value) -> System.out.println(key + ": " + value));
}
public void writeHeader(String newFilename) throws IOException {
if (newFilename == null) {
newFilename = filename + ".modified";
}
byte[] header = packHeader();
try (FileInputStream fis = new FileInputStream(filename);
FileOutputStream fos = new FileOutputStream(newFilename)) {
fos.write(header);
fis.skip(512);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
}
System.out.println("Modified file written to " + newFilename);
}
private byte[] packHeader() {
ByteBuffer bb = ByteBuffer.allocate(512).order(ByteOrder.LITTLE_ENDIAN);
// Signature
String[] sigStr = ((String) properties.get("Signature")).split(" ");
for (String s : sigStr) {
bb.put((byte) Integer.parseInt(s.substring(2), 16));
}
// CLSID
String[] clsidStr = ((String) properties.get("CLSID")).split(" ");
for (String s : clsidStr) {
bb.put((byte) Integer.parseInt(s.substring(2), 16));
}
// Minor Version
bb.putShort((short) Integer.parseInt(((String) properties.get("Minor Version")).substring(2), 16));
// Major Version
bb.putShort((short) Integer.parseInt(((String) properties.get("Major Version")).substring(2), 16));
// Byte Order
bb.putShort((short) Integer.parseInt(((String) properties.get("Byte Order")).substring(2), 16));
// Sector Shift
bb.putShort(((Integer) properties.get("Sector Shift")).shortValue());
// Mini Sector Shift
bb.putShort(((Integer) properties.get("Mini Sector Shift")).shortValue());
// Reserved
bb.putShort((short) Integer.parseInt(((String) properties.get("Reserved")).substring(2), 16));
// Reserved1
bb.putInt(Integer.parseInt(((String) properties.get("Reserved1")).substring(2), 16));
// Number of Directory Sectors
bb.putInt((Integer) properties.get("Number of Directory Sectors"));
// Number of FAT Sectors
bb.putInt((Integer) properties.get("Number of FAT Sectors"));
// First Directory Sector Location
bb.putInt(Integer.parseInt(((String) properties.get("First Directory Sector Location")).substring(2), 16));
// Transaction Signature Number
bb.putInt((Integer) properties.get("Transaction Signature Number"));
// Mini Stream Cutoff Size
bb.putInt((Integer) properties.get("Mini Stream Cutoff Size"));
// First Mini FAT Sector Location
bb.putInt(Integer.parseInt(((String) properties.get("First Mini FAT Sector Location")).substring(2), 16));
// Number of Mini FAT Sectors
bb.putInt((Integer) properties.get("Number of Mini FAT Sectors"));
// First DIFAT Sector Location
bb.putInt(Integer.parseInt(((String) properties.get("First DIFAT Sector Location")).substring(2), 16));
// Number of DIFAT Sectors
bb.putInt((Integer) properties.get("Number of DIFAT Sectors"));
// DIFAT
String[] difatStr = ((String) properties.get("DIFAT")).split(" ");
for (String s : difatStr) {
bb.putInt(Integer.parseInt(s.substring(2), 16));
}
return bb.array();
}
// Example usage:
// public static void main(String[] args) throws IOException {
// XLSHeaderParser parser = new XLSHeaderParser("example.xls");
// parser.printProperties();
// parser.writeHeader(null);
// }
}
- JavaScript class for .XLS handling (Node.js, using fs module):
const fs = require('fs');
class XLSHeaderParser {
constructor(filename) {
this.filename = filename;
this.properties = {};
this.readHeader();
}
readHeader() {
const buffer = fs.readFileSync(this.filename, { encoding: null });
if (buffer.length < 512) {
throw new Error('File too small to be a valid .XLS');
}
const dataView = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
// Signature
let sig = '';
for (let i = 0; i < 8; i++) sig += `0x${dataView.getUint8(i).toString(16).padStart(2, '0')} `;
this.properties['Signature'] = sig.trim();
// CLSID
let clsid = '';
for (let i = 8; i < 24; i++) clsid += `0x${dataView.getUint8(i).toString(16).padStart(2, '0')} `;
this.properties['CLSID'] = clsid.trim();
// Minor Version
this.properties['Minor Version'] = `0x${dataView.getUint16(24, true).toString(16).padStart(4, '0')}`;
// Major Version
this.properties['Major Version'] = `0x${dataView.getUint16(26, true).toString(16).padStart(4, '0')}`;
// Byte Order
this.properties['Byte Order'] = `0x${dataView.getUint16(28, true).toString(16).padStart(4, '0')}`;
// Sector Shift
this.properties['Sector Shift'] = dataView.getUint16(30, true);
// Mini Sector Shift
this.properties['Mini Sector Shift'] = dataView.getUint16(32, true);
// Reserved
this.properties['Reserved'] = `0x${dataView.getUint16(34, true).toString(16).padStart(4, '0')}`;
// Reserved1
this.properties['Reserved1'] = `0x${dataView.getUint32(36, true).toString(16).padStart(8, '0')}`;
// Number of Directory Sectors
this.properties['Number of Directory Sectors'] = dataView.getUint32(40, true);
// Number of FAT Sectors
this.properties['Number of FAT Sectors'] = dataView.getUint32(44, true);
// First Directory Sector Location
this.properties['First Directory Sector Location'] = `0x${dataView.getUint32(48, true).toString(16).padStart(8, '0')}`;
// Transaction Signature Number
this.properties['Transaction Signature Number'] = dataView.getUint32(52, true);
// Mini Stream Cutoff Size
this.properties['Mini Stream Cutoff Size'] = dataView.getUint32(56, true);
// First Mini FAT Sector Location
this.properties['First Mini FAT Sector Location'] = `0x${dataView.getUint32(60, true).toString(16).padStart(8, '0')}`;
// Number of Mini FAT Sectors
this.properties['Number of Mini FAT Sectors'] = dataView.getUint32(64, true);
// First DIFAT Sector Location
this.properties['First DIFAT Sector Location'] = `0x${dataView.getUint32(68, true).toString(16).padStart(8, '0')}`;
// Number of DIFAT Sectors
this.properties['Number of DIFAT Sectors'] = dataView.getUint32(72, true);
// DIFAT
let difat = '';
for (let i = 0; i < 109; i++) {
difat += `0x${dataView.getUint32(76 + i * 4, true).toString(16).padStart(8, '0')} `;
}
this.properties['DIFAT'] = difat.trim();
}
printProperties() {
for (const [key, value] of Object.entries(this.properties)) {
console.log(`${key}: ${value}`);
}
}
writeHeader(newFilename = null) {
if (!newFilename) {
newFilename = this.filename + '.modified';
}
const originalBuffer = fs.readFileSync(this.filename);
const headerBuffer = this.packHeader();
const newBuffer = Buffer.concat([headerBuffer, originalBuffer.slice(512)]);
fs.writeFileSync(newFilename, newBuffer);
console.log(`Modified file written to ${newFilename}`);
}
packHeader() {
const buffer = Buffer.alloc(512);
const dataView = new DataView(buffer.buffer);
// Signature
const sigParts = this.properties['Signature'].split(' ');
sigParts.forEach((part, i) => dataView.setUint8(i, parseInt(part, 16)));
// CLSID
const clsidParts = this.properties['CLSID'].split(' ');
clsidParts.forEach((part, i) => dataView.setUint8(8 + i, parseInt(part, 16)));
// Minor Version
dataView.setUint16(24, parseInt(this.properties['Minor Version'], 16), true);
// Major Version
dataView.setUint16(26, parseInt(this.properties['Major Version'], 16), true);
// Byte Order
dataView.setUint16(28, parseInt(this.properties['Byte Order'], 16), true);
// Sector Shift
dataView.setUint16(30, this.properties['Sector Shift'], true);
// Mini Sector Shift
dataView.setUint16(32, this.properties['Mini Sector Shift'], true);
// Reserved
dataView.setUint16(34, parseInt(this.properties['Reserved'], 16), true);
// Reserved1
dataView.setUint32(36, parseInt(this.properties['Reserved1'], 16), true);
// Number of Directory Sectors
dataView.setUint32(40, this.properties['Number of Directory Sectors'], true);
// Number of FAT Sectors
dataView.setUint32(44, this.properties['Number of FAT Sectors'], true);
// First Directory Sector Location
dataView.setUint32(48, parseInt(this.properties['First Directory Sector Location'], 16), true);
// Transaction Signature Number
dataView.setUint32(52, this.properties['Transaction Signature Number'], true);
// Mini Stream Cutoff Size
dataView.setUint32(56, this.properties['Mini Stream Cutoff Size'], true);
// First Mini FAT Sector Location
dataView.setUint32(60, parseInt(this.properties['First Mini FAT Sector Location'], 16), true);
// Number of Mini FAT Sectors
dataView.setUint32(64, this.properties['Number of Mini FAT Sectors'], true);
// First DIFAT Sector Location
dataView.setUint32(68, parseInt(this.properties['First DIFAT Sector Location'], 16), true);
// Number of DIFAT Sectors
dataView.setUint32(72, this.properties['Number of DIFAT Sectors'], true);
// DIFAT
const difatParts = this.properties['DIFAT'].split(' ');
difatParts.forEach((part, i) => dataView.setUint32(76 + i * 4, parseInt(part, 16), true));
return buffer;
}
}
// Example usage:
// const parser = new XLSHeaderParser('example.xls');
// parser.printProperties();
// parser.writeHeader();
- C++ class for .XLS handling (using and ):
#include <iostream>
#include <fstream>
#include <iomanip>
#include <string>
#include <map>
#include <vector>
#include <cstdint>
class XLSHeaderParser {
private:
std::string filename;
std::map<std::string, std::string> properties;
public:
XLSHeaderParser(const std::string& fn) : filename(fn) {
readHeader();
}
void readHeader() {
std::ifstream file(filename, std::ios::binary);
if (!file) {
throw std::runtime_error("Cannot open file");
}
std::vector<uint8_t> header(512);
file.read(reinterpret_cast<char*>(header.data()), 512);
if (file.gcount() < 512) {
throw std::runtime_error("File too small to be a valid .XLS");
}
// Signature
std::ostringstream sig;
for (int i = 0; i < 8; ++i) {
sig << "0x" << std::hex << std::setw(2) << std::setfill('0') << (header[i] & 0xFF) << " ";
}
properties["Signature"] = sig.str().substr(0, sig.str().size() - 1);
// CLSID
std::ostringstream clsid;
for (int i = 8; i < 24; ++i) {
clsid << "0x" << std::hex << std::setw(2) << std::setfill('0') << (header[i] & 0xFF) << " ";
}
properties["CLSID"] = clsid.str().substr(0, clsid.str().size() - 1);
// Helper to get little-endian values
auto getUint16 = [&header](int offset) { return *reinterpret_cast<const uint16_t*>(&header[offset]); };
auto getUint32 = [&header](int offset) { return *reinterpret_cast<const uint32_t*>(&header[offset]); };
// Minor Version
properties["Minor Version"] = "0x" + std::to_string(getUint16(24));
std::ostringstream oss;
oss << "0x" << std::hex << std::setw(4) << std::setfill('0') << getUint16(24);
properties["Minor Version"] = oss.str();
// Major Version
oss.str("");
oss << "0x" << std::hex << std::setw(4) << std::setfill('0') << getUint16(26);
properties["Major Version"] = oss.str();
// Byte Order
oss.str("");
oss << "0x" << std::hex << std::setw(4) << std::setfill('0') << getUint16(28);
properties["Byte Order"] = oss.str();
// Sector Shift
properties["Sector Shift"] = std::to_string(getUint16(30));
// Mini Sector Shift
properties["Mini Sector Shift"] = std::to_string(getUint16(32));
// Reserved
oss.str("");
oss << "0x" << std::hex << std::setw(4) << std::setfill('0') << getUint16(34);
properties["Reserved"] = oss.str();
// Reserved1
oss.str("");
oss << "0x" << std::hex << std::setw(8) << std::setfill('0') << getUint32(36);
properties["Reserved1"] = oss.str();
// Number of Directory Sectors
properties["Number of Directory Sectors"] = std::to_string(getUint32(40));
// Number of FAT Sectors
properties["Number of FAT Sectors"] = std::to_string(getUint32(44));
// First Directory Sector Location
oss.str("");
oss << "0x" << std::hex << std::setw(8) << std::setfill('0') << getUint32(48);
properties["First Directory Sector Location"] = oss.str();
// Transaction Signature Number
properties["Transaction Signature Number"] = std::to_string(getUint32(52));
// Mini Stream Cutoff Size
properties["Mini Stream Cutoff Size"] = std::to_string(getUint32(56));
// First Mini FAT Sector Location
oss.str("");
oss << "0x" << std::hex << std::setw(8) << std::setfill('0') << getUint32(60);
properties["First Mini FAT Sector Location"] = oss.str();
// Number of Mini FAT Sectors
properties["Number of Mini FAT Sectors"] = std::to_string(getUint32(64));
// First DIFAT Sector Location
oss.str("");
oss << "0x" << std::hex << std::setw(8) << std::setfill('0') << getUint32(68);
properties["First DIFAT Sector Location"] = oss.str();
// Number of DIFAT Sectors
properties["Number of DIFAT Sectors"] = std::to_string(getUint32(72));
// DIFAT
std::ostringstream difat;
for (int i = 0; i < 109; ++i) {
uint32_t val = getUint32(76 + i * 4);
difat << "0x" << std::hex << std::setw(8) << std::setfill('0') << val << " ";
}
properties["DIFAT"] = difat.str().substr(0, difat.str().size() - 1);
}
void printProperties() const {
for (const auto& prop : properties) {
std::cout << prop.first << ": " << prop.second << std::endl;
}
}
void writeHeader(const std::string& newFilename = "") const {
std::string outFn = newFilename.empty() ? filename + ".modified" : newFilename;
std::ifstream inFile(filename, std::ios::binary);
std::ofstream outFile(outFn, std::ios::binary);
if (!inFile || !outFile) {
throw std::runtime_error("File I/O error");
}
std::vector<uint8_t> header(512, 0);
// Signature
auto parseHexBytes = [](const std::string& s, uint8_t* dest) {
std::istringstream iss(s);
std::string token;
int idx = 0;
while (iss >> token) {
dest[idx++] = static_cast<uint8_t>(std::stoul(token.substr(2), nullptr, 16));
}
};
parseHexBytes(properties.at("Signature"), header.data());
// CLSID
parseHexBytes(properties.at("CLSID"), header.data() + 8);
// Minor Version
*reinterpret_cast<uint16_t*>(&header[24]) = static_cast<uint16_t>(std::stoul(properties.at("Minor Version").substr(2), nullptr, 16));
// Major Version
*reinterpret_cast<uint16_t*>(&header[26]) = static_cast<uint16_t>(std::stoul(properties.at("Major Version").substr(2), nullptr, 16));
// Byte Order
*reinterpret_cast<uint16_t*>(&header[28]) = static_cast<uint16_t>(std::stoul(properties.at("Byte Order").substr(2), nullptr, 16));
// Sector Shift
*reinterpret_cast<uint16_t*>(&header[30]) = static_cast<uint16_t>(std::stoi(properties.at("Sector Shift")));
// Mini Sector Shift
*reinterpret_cast<uint16_t*>(&header[32]) = static_cast<uint16_t>(std::stoi(properties.at("Mini Sector Shift")));
// Reserved
*reinterpret_cast<uint16_t*>(&header[34]) = static_cast<uint16_t>(std::stoul(properties.at("Reserved").substr(2), nullptr, 16));
// Reserved1
*reinterpret_cast<uint32_t*>(&header[36]) = std::stoul(properties.at("Reserved1").substr(2), nullptr, 16);
// Number of Directory Sectors
*reinterpret_cast<uint32_t*>(&header[40]) = std::stoul(properties.at("Number of Directory Sectors"));
// Number of FAT Sectors
*reinterpret_cast<uint32_t*>(&header[44]) = std::stoul(properties.at("Number of FAT Sectors"));
// First Directory Sector Location
*reinterpret_cast<uint32_t*>(&header[48]) = std::stoul(properties.at("First Directory Sector Location").substr(2), nullptr, 16);
// Transaction Signature Number
*reinterpret_cast<uint32_t*>(&header[52]) = std::stoul(properties.at("Transaction Signature Number"));
// Mini Stream Cutoff Size
*reinterpret_cast<uint32_t*>(&header[56]) = std::stoul(properties.at("Mini Stream Cutoff Size"));
// First Mini FAT Sector Location
*reinterpret_cast<uint32_t*>(&header[60]) = std::stoul(properties.at("First Mini FAT Sector Location").substr(2), nullptr, 16);
// Number of Mini FAT Sectors
*reinterpret_cast<uint32_t*>(&header[64]) = std::stoul(properties.at("Number of Mini FAT Sectors"));
// First DIFAT Sector Location
*reinterpret_cast<uint32_t*>(&header[68]) = std::stoul(properties.at("First DIFAT Sector Location").substr(2), nullptr, 16);
// Number of DIFAT Sectors
*reinterpret_cast<uint32_t*>(&header[72]) = std::stoul(properties.at("Number of DIFAT Sectors"));
// DIFAT
std::istringstream difatIss(properties.at("DIFAT"));
std::string token;
int idx = 76;
while (difatIss >> token) {
*reinterpret_cast<uint32_t*>(&header[idx]) = std::stoul(token.substr(2), nullptr, 16);
idx += 4;
}
outFile.write(reinterpret_cast<const char*>(header.data()), 512);
inFile.seekg(512);
char buf[4096];
while (inFile.read(buf, sizeof(buf))) {
outFile.write(buf, inFile.gcount());
}
outFile.write(buf, inFile.gcount()); // Last chunk
std::cout << "Modified file written to " << outFn << std::endl;
}
};
// Example usage:
// int main() {
// try {
// XLSHeaderParser parser("example.xls");
// parser.printProperties();
// parser.writeHeader();
// } catch (const std::exception& e) {
// std::cerr << e.what() << std::endl;
// }
// return 0;
// }