Task 831: .XEX File Format
Task 831: .XEX File Format
File Format Specifications for .XEX
The .XEX file format refers to the Atari 8-bit binary load file format, used for executables, demos, games, and other programs on Atari 8-bit computers. It is a segmented binary format designed for loading data into specific memory addresses. The format supports multiple segments, with special handling for initialization and run vectors.
The file typically starts with a magic header of 0xFF 0xFF (required for the first segment, optional for subsequent ones). Each segment consists of a 2-byte start address (little-endian), a 2-byte end address (little-endian), followed by the data bytes (length = end - start + 1).
Special segments:
- If a segment targets addresses 0x02E2 to 0x02E3, its 2-byte data represents the INIT address (called during loading).
- If a segment targets addresses 0x02E0 to 0x02E1, its 2-byte data represents the RUN address (called after loading).
The format is intrinsic to the Atari DOS file system, where files are loaded sequentially into memory, allowing for staged execution and hardware-specific initialization.
List of all properties intrinsic to this file format:
- Magic header: 0xFF 0xFF (2 bytes, present at the start).
- Segments: A list of memory segments, each with:
- Start address (16-bit unsigned integer, little-endian).
- End address (16-bit unsigned integer, little-endian).
- Data length (calculated as end - start + 1).
- Data bytes (binary content loaded into the address range).
- INIT address: Optional 16-bit address (little-endian) if a segment loads to 0x02E2-0x02E3.
- RUN address: Optional 16-bit address (little-endian) if a segment loads to 0x02E0-0x02E1.
Two direct download links for .XEX files:
- https://wowroms.com/en/roms/atari-800/download-boulder-dash-xex/74071.html
- https://archive.org/download/tosec-main/Atari/8bit/Demos/[XEX]/Atari 8bit - Demos - [XEX] (TOSEC-v2025-01-15).zip/Antic Space Sound (19xx)(Antic)(pl)/Antic Space Sound (19xx)(Antic)(pl).xex
Ghost blog embedded HTML JavaScript for drag-and-drop .XEX file dump:
- Python class:
import struct
import sys
class XEXFile:
def __init__(self, filepath):
self.filepath = filepath
self.magic = None
self.segments = [] # list of (start, end, data)
self.init_addr = None
self.run_addr = None
def decode(self):
with open(self.filepath, 'rb') as f:
data = f.read()
offset = 0
if len(data) < 2 or data[offset:offset+2] != b'\xff\xff':
raise ValueError("Invalid magic header")
self.magic = b'\xff\xff'
offset += 2
while offset < len(data):
if offset + 4 > len(data):
break
start, end = struct.unpack('<HH', data[offset:offset+4])
length = end - start + 1
offset += 4
if offset + length > len(data):
raise ValueError("Invalid segment length")
segment_data = data[offset:offset+length]
self.segments.append((start, end, segment_data))
if start == 0x02E2 and end == 0x02E3 and length == 2:
self.init_addr = struct.unpack('<H', segment_data)[0]
elif start == 0x02E0 and end == 0x02E1 and length == 2:
self.run_addr = struct.unpack('<H', segment_data)[0]
offset += length
def print_properties(self):
if self.magic:
print("Magic header: 0xFF 0xFF")
for i, (start, end, data) in enumerate(self.segments, 1):
print(f"Segment {i}:")
print(f" Start address: 0x{start:04X}")
print(f" End address: 0x{end:04X}")
print(f" Data length: {len(data)} bytes")
if self.init_addr is not None:
print(f"INIT address: 0x{self.init_addr:04X}")
if self.run_addr is not None:
print(f"RUN address: 0x{self.run_addr:04X}")
def write(self, output_path):
with open(output_path, 'wb') as f:
f.write(self.magic)
for start, end, data in self.segments:
f.write(struct.pack('<HH', start, end))
f.write(data)
# Example usage:
# if __name__ == "__main__":
# xex = XEXFile("example.xex")
# xex.decode()
# xex.print_properties()
# xex.write("output.xex")
- Java class:
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class XEXFile {
private String filepath;
private byte[] magic;
private java.util.List<Segment> segments = new java.util.ArrayList<>();
private Integer initAddr;
private Integer runAddr;
static class Segment {
int start;
int end;
byte[] data;
Segment(int start, int end, byte[] data) {
this.start = start;
this.end = end;
this.data = data;
}
}
public XEXFile(String filepath) {
this.filepath = filepath;
}
public void decode() throws IOException {
try (FileInputStream fis = new FileInputStream(filepath);
BufferedInputStream bis = new BufferedInputStream(fis)) {
byte[] data = bis.readAllBytes();
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
int offset = 0;
if (bb.get(offset) != (byte)0xFF || bb.get(offset + 1) != (byte)0xFF) {
throw new IllegalArgumentException("Invalid magic header");
}
magic = new byte[]{ (byte)0xFF, (byte)0xFF };
offset += 2;
while (offset < data.length) {
if (offset + 4 > data.length) break;
int start = Short.toUnsignedInt(bb.getShort(offset));
int end = Short.toUnsignedInt(bb.getShort(offset + 2));
int length = end - start + 1;
offset += 4;
if (offset + length > data.length) {
throw new IllegalArgumentException("Invalid segment length");
}
byte[] segmentData = new byte[length];
System.arraycopy(data, offset, segmentData, 0, length);
segments.add(new Segment(start, end, segmentData));
if (start == 0x02E2 && end == 0x02E3 && length == 2) {
ByteBuffer sbb = ByteBuffer.wrap(segmentData).order(ByteOrder.LITTLE_ENDIAN);
initAddr = Short.toUnsignedInt(sbb.getShort(0));
} else if (start == 0x02E0 && end == 0x02E1 && length == 2) {
ByteBuffer sbb = ByteBuffer.wrap(segmentData).order(ByteOrder.LITTLE_ENDIAN);
runAddr = Short.toUnsignedInt(sbb.getShort(0));
}
offset += length;
}
}
}
public void printProperties() {
if (magic != null) {
System.out.println("Magic header: 0xFF 0xFF");
}
for (int i = 0; i < segments.size(); i++) {
Segment seg = segments.get(i);
System.out.println("Segment " + (i + 1) + ":");
System.out.printf(" Start address: 0x%04X%n", seg.start);
System.out.printf(" End address: 0x%04X%n", seg.end);
System.out.println(" Data length: " + seg.data.length + " bytes");
}
if (initAddr != null) {
System.out.printf("INIT address: 0x%04X%n", initAddr);
}
if (runAddr != null) {
System.out.printf("RUN address: 0x%04X%n", runAddr);
}
}
public void write(String outputPath) throws IOException {
try (FileOutputStream fos = new FileOutputStream(outputPath)) {
fos.write(magic);
for (Segment seg : segments) {
ByteBuffer bb = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
bb.putShort((short)seg.start);
bb.putShort((short)seg.end);
fos.write(bb.array());
fos.write(seg.data);
}
}
}
// Example usage:
// public static void main(String[] args) throws IOException {
// XEXFile xex = new XEXFile("example.xex");
// xex.decode();
// xex.printProperties();
// xex.write("output.xex");
// }
}
- JavaScript class:
const fs = require('fs'); // For Node.js environment
class XEXFile {
constructor(filepath) {
this.filepath = filepath;
this.magic = null;
this.segments = []; // array of {start, end, data: Buffer}
this.initAddr = null;
this.runAddr = null;
}
decode() {
const data = fs.readFileSync(this.filepath);
let offset = 0;
if (data[offset] !== 0xFF || data[offset + 1] !== 0xFF) {
throw new Error('Invalid magic header');
}
this.magic = Buffer.from([0xFF, 0xFF]);
offset += 2;
while (offset < data.length) {
if (offset + 4 > data.length) break;
const start = data.readUInt16LE(offset);
const end = data.readUInt16LE(offset + 2);
const length = end - start + 1;
offset += 4;
if (offset + length > data.length) {
throw new Error('Invalid segment length');
}
const segmentData = data.slice(offset, offset + length);
this.segments.push({start, end, data: segmentData});
if (start === 0x02E2 && end === 0x02E3 && length === 2) {
this.initAddr = segmentData.readUInt16LE(0);
} else if (start === 0x02E0 && end === 0x02E1 && length === 2) {
this.runAddr = segmentData.readUInt16LE(0);
}
offset += length;
}
}
printProperties() {
if (this.magic) {
console.log('Magic header: 0xFF 0xFF');
}
this.segments.forEach((seg, i) => {
console.log(`Segment ${i + 1}:`);
console.log(` Start address: 0x${seg.start.toString(16).toUpperCase()}`);
console.log(` End address: 0x${seg.end.toString(16).toUpperCase()}`);
console.log(` Data length: ${seg.data.length} bytes`);
});
if (this.initAddr !== null) {
console.log(`INIT address: 0x${this.initAddr.toString(16).toUpperCase()}`);
}
if (this.runAddr !== null) {
console.log(`RUN address: 0x${this.runAddr.toString(16).toUpperCase()}`);
}
}
write(outputPath) {
const buffers = [this.magic];
this.segments.forEach(seg => {
const header = Buffer.alloc(4);
header.writeUInt16LE(seg.start, 0);
header.writeUInt16LE(seg.end, 2);
buffers.push(header, seg.data);
});
fs.writeFileSync(outputPath, Buffer.concat(buffers));
}
}
// Example usage:
// const xex = new XEXFile('example.xex');
// xex.decode();
// xex.printProperties();
// xex.write('output.xex');
- C class (using C++ for class support):
#include <iostream>
#include <fstream>
#include <vector>
#include <cstdint>
#include <cstring>
struct Segment {
uint16_t start;
uint16_t end;
std::vector<uint8_t> data;
};
class XEXFile {
private:
std::string filepath;
uint8_t magic[2];
std::vector<Segment> segments;
uint16_t init_addr;
uint16_t run_addr;
bool has_init;
bool has_run;
public:
XEXFile(const std::string& fp) : filepath(fp), has_init(false), has_run(false) {
std::memset(magic, 0, sizeof(magic));
init_addr = 0;
run_addr = 0;
}
void decode() {
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
if (!file) {
throw std::runtime_error("Cannot open file");
}
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<uint8_t> data(size);
if (!file.read(reinterpret_cast<char*>(data.data()), size)) {
throw std::runtime_error("Error reading file");
}
size_t offset = 0;
if (data[offset] != 0xFF || data[offset + 1] != 0xFF) {
throw std::runtime_error("Invalid magic header");
}
magic[0] = 0xFF;
magic[1] = 0xFF;
offset += 2;
while (offset < data.size()) {
if (offset + 4 > data.size()) break;
uint16_t start = *reinterpret_cast<uint16_t*>(&data[offset]);
uint16_t end = *reinterpret_cast<uint16_t*>(&data[offset + 2]);
size_t length = static_cast<size_t>(end) - start + 1;
offset += 4;
if (offset + length > data.size()) {
throw std::runtime_error("Invalid segment length");
}
std::vector<uint8_t> seg_data(data.begin() + offset, data.begin() + offset + length);
segments.push_back({start, end, seg_data});
if (start == 0x02E2 && end == 0x02E3 && length == 2) {
init_addr = *reinterpret_cast<uint16_t*>(seg_data.data());
has_init = true;
} else if (start == 0x02E0 && end == 0x02E1 && length == 2) {
run_addr = *reinterpret_cast<uint16_t*>(seg_data.data());
has_run = true;
}
offset += length;
}
}
void printProperties() const {
if (magic[0] == 0xFF && magic[1] == 0xFF) {
std::cout << "Magic header: 0xFF 0xFF" << std::endl;
}
for (size_t i = 0; i < segments.size(); ++i) {
const auto& seg = segments[i];
std::cout << "Segment " << (i + 1) << ":" << std::endl;
std::printf(" Start address: 0x%04X\n", seg.start);
std::printf(" End address: 0x%04X\n", seg.end);
std::cout << " Data length: " << seg.data.size() << " bytes" << std::endl;
}
if (has_init) {
std::printf("INIT address: 0x%04X\n", init_addr);
}
if (has_run) {
std::printf("RUN address: 0x%04X\n", run_addr);
}
}
void write(const std::string& outputPath) const {
std::ofstream file(outputPath, std::ios::binary);
if (!file) {
throw std::runtime_error("Cannot open output file");
}
file.write(reinterpret_cast<const char*>(magic), 2);
for (const auto& seg : segments) {
uint16_t start = seg.start;
uint16_t end = seg.end;
file.write(reinterpret_cast<const char*>(&start), 2);
file.write(reinterpret_cast<const char*>(&end), 2);
file.write(reinterpret_cast<const char*>(seg.data.data()), seg.data.size());
}
}
};
// Example usage:
// int main() {
// try {
// XEXFile xex("example.xex");
// xex.decode();
// xex.printProperties();
// xex.write("output.xex");
// } catch (const std::exception& e) {
// std::cerr << e.what() << std::endl;
// }
// return 0;
// }