Task 183: .EPA File Format

Task 183: .EPA File Format

1. List of all the properties of this file format intrinsic to its file system

The .EPA file format is the Award BIOS logo format (also known as EPA or AWBM), used for boot screen images in Award BIOS firmware. It has two main versions with different structures. The properties are the structural fields in the file format itself (e.g., headers and data blocks), as these are intrinsic to how the file is organized and interpreted. File system-level properties like size or timestamps are not specific to the format and are not included. The properties for each version are as follows:

Version 1 (Text-mode character graphics-based):

  • Width: 1-byte unsigned integer (uint8_t) representing image width in character cells (typically 17; each cell is 8 dots wide).
  • Height: 1-byte unsigned integer (uint8_t) representing image height in character cells (typically 6 or 9; each cell is 14 dots high).
  • Color Map (cmap): Array of bytes (width × height bytes long), containing foreground-background color pairs for each character cell (stored row-by-row, left-to-right; lower nybble is foreground color).
  • Bitmap: Array of bytes (width × height × 14 bytes long), containing bitmaps for each character cell (in the same order as cmap; each cell is 14 bytes, with each byte representing one row of dots, top to bottom, MSB leftmost).
  • Logo: 70-byte fixed array representing a small monochrome Award Software logo (3x2 cells, 24x28 pixels; stored in bitmap format, top-left corner assumed empty).

Version 2 (Standard image format, with 4-bit planar or 8-bit packed variants; little-endian):

  • Magic: 4-byte fixed string ('A' 'W' 'B' 'M' or hex 41 57 42 4D).
  • Width: 2-byte unsigned integer (uint16_t) representing image width in pixels (common values: 136, 640).
  • Height: 2-byte unsigned integer (uint16_t) representing image height in pixels (common values: 84, 126, 480).
  • Data: Variable-length array of image pixel data (for 4-bit: (width + padding to multiple of 8) × height / 2 bytes; for 8-bit: width × height bytes; rows left-to-right, planes or pixels in order).
  • PalMagic: 4-byte fixed string ('R' 'G' 'B' ' ' or hex 52 47 42 20; optional in some 4-bit files).
  • PalData: Variable-length palette array (for 4-bit: 16 × 3 bytes; for 8-bit: 256 × 3 bytes; triplets in range 0-63, order BGR or RGB depending on variant).

Version detection: Files starting with 'AWBM' are Version 2; others are Version 1 if the first two bytes are plausible width/height values (e.g., 0x11 0x06).

3. Ghost blog embedded HTML JavaScript for drag-and-drop .EPA file dump

Below is a complete, self-contained HTML page with embedded JavaScript that can be embedded in a Ghost blog post (or any HTML-enabled blog). It allows users to drag and drop a .EPA file, parses it (detecting version), and dumps all properties to the screen in a readable format.

.EPA File Property Dumper
Drag and drop a .EPA file here

4. Python class for .EPA file handling

import struct

class EpaFile:
    def __init__(self):
        self.version = None
        self.properties = {}

    def open(self, filename, mode='rb'):
        self.file = open(filename, mode)
        return self

    def close(self):
        self.file.close()

    def read(self):
        data = self.file.read()
        self.file.seek(0)
        magic = data[0:4].decode('ascii', errors='ignore')
        if magic == 'AWBM':
            self.version = 2
            self.properties['magic'] = magic
            self.properties['width'] = struct.unpack('<H', data[4:6])[0]
            self.properties['height'] = struct.unpack('<H', data[6:8])[0]
            offset = 8
            # Assume 4-bit for simplicity; can add detection
            data_length = (self.properties['width'] + (7 - self.properties['width'] % 8)) * self.properties['height'] // 2
            self.properties['data'] = data[offset:offset + data_length]
            offset += data_length
            self.properties['palMagic'] = data[offset:offset+4].decode('ascii', errors='ignore')
            offset += 4
            pal_size = 48  # 4-bit
            self.properties['palData'] = data[offset:offset + pal_size]
        else:
            self.version = 1
            self.properties['width'] = data[0]
            self.properties['height'] = data[1]
            offset = 2
            cmap_length = self.properties['width'] * self.properties['height']
            self.properties['cmap'] = data[offset:offset + cmap_length]
            offset += cmap_length
            bitmap_length = self.properties['width'] * self.properties['height'] * 14
            self.properties['bitmap'] = data[offset:offset + bitmap_length]
            offset += bitmap_length
            self.properties['logo'] = data[offset:offset + 70]

    def write(self, filename):
        with open(filename, 'wb') as f:
            if self.version == 2:
                f.write(self.properties['magic'].encode('ascii'))
                f.write(struct.pack('<H', self.properties['width']))
                f.write(struct.pack('<H', self.properties['height']))
                f.write(self.properties['data'])
                f.write(self.properties['palMagic'].encode('ascii'))
                f.write(self.properties['palData'])
            elif self.version == 1:
                f.write(struct.pack('B', self.properties['width']))
                f.write(struct.pack('B', self.properties['height']))
                f.write(self.properties['cmap'])
                f.write(self.properties['bitmap'])
                f.write(self.properties['logo'])

    def print_properties(self):
        print(f"Version: {self.version}")
        for key, value in self.properties.items():
            print(f"{key}: {value}")

# Example usage:
# epa = EpaFile().open('example.epa')
# epa.read()
# epa.print_properties()
# epa.close()
# To write, set properties and call write

5. Java class for .EPA file handling

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class EpaFile {
    private int version;
    private ByteBuffer buffer;
    private int width, height;
    private byte[] cmap, bitmap, logo, data, palData;
    private String magic, palMagic;

    public void open(String filename) throws IOException {
        File file = new File(filename);
        byte[] bytes = new byte[(int) file.length()];
        try (FileInputStream fis = new FileInputStream(file)) {
            fis.read(bytes);
        }
        buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
    }

    public void read() {
        buffer.position(0);
        byte[] magicBytes = new byte[4];
        buffer.get(magicBytes);
        magic = new String(magicBytes);
        if (magic.equals("AWBM")) {
            version = 2;
            width = buffer.getShort();
            height = buffer.getShort();
            // Assume 4-bit
            int dataLength = (width + (7 - width % 8)) * height / 2;
            data = new byte[dataLength];
            buffer.get(data);
            byte[] palMagicBytes = new byte[4];
            buffer.get(palMagicBytes);
            palMagic = new String(palMagicBytes);
            int palSize = 48;
            palData = new byte[palSize];
            buffer.get(palData);
        } else {
            buffer.position(0);
            version = 1;
            width = Byte.toUnsignedInt(buffer.get());
            height = Byte.toUnsignedInt(buffer.get());
            int cmapLength = width * height;
            cmap = new byte[cmapLength];
            buffer.get(cmap);
            int bitmapLength = width * height * 14;
            bitmap = new byte[bitmapLength];
            buffer.get(bitmap);
            logo = new byte[70];
            buffer.get(logo);
        }
    }

    public void write(String filename) throws IOException {
        try (FileOutputStream fos = new FileOutputStream(filename)) {
            ByteBuffer outBuffer = ByteBuffer.allocate(1024 * 1024).order(ByteOrder.LITTLE_ENDIAN); // Oversize
            if (version == 2) {
                outBuffer.put(magic.getBytes());
                outBuffer.putShort((short) width);
                outBuffer.putShort((short) height);
                outBuffer.put(data);
                outBuffer.put(palMagic.getBytes());
                outBuffer.put(palData);
            } else if (version == 1) {
                outBuffer.put((byte) width);
                outBuffer.put((byte) height);
                outBuffer.put(cmap);
                outBuffer.put(bitmap);
                outBuffer.put(logo);
            }
            fos.write(outBuffer.array(), 0, outBuffer.position());
        }
    }

    public void printProperties() {
        System.out.println("Version: " + version);
        System.out.println("Width: " + width);
        System.out.println("Height: " + height);
        if (version == 2) {
            System.out.println("Magic: " + magic);
            System.out.println("Data: (binary data, length " + data.length + ")");
            System.out.println("PalMagic: " + palMagic);
            System.out.println("PalData: (binary data, length " + palData.length + ")");
        } else {
            System.out.println("CMap: (binary data, length " + cmap.length + ")");
            System.out.println("Bitmap: (binary data, length " + bitmap.length + ")");
            System.out.println("Logo: (binary data, length " + logo.length + ")");
        }
    }

    // Example usage:
    // EpaFile epa = new EpaFile();
    // epa.open("example.epa");
    // epa.read();
    // epa.printProperties();
    // epa.write("output.epa");
}

6. JavaScript class for .EPA file handling

class EpaFile {
    constructor() {
        this.version = null;
        this.properties = {};
    }

    async open(file) {
        const arrayBuffer = await file.arrayBuffer();
        this.dataView = new DataView(arrayBuffer);
    }

    read() {
        let offset = 0;
        const magic = String.fromCharCode(this.dataView.getUint8(offset), this.dataView.getUint8(offset+1), this.dataView.getUint8(offset+2), this.dataView.getUint8(offset+3));
        if (magic === 'AWBM') {
            this.version = 2;
            this.properties.magic = magic;
            this.properties.width = this.dataView.getUint16(4, true);
            this.properties.height = this.dataView.getUint16(6, true);
            offset = 8;
            // Assume 4-bit
            const dataLength = (this.properties.width + (7 - this.properties.width % 8)) * this.properties.height / 2;
            this.properties.data = new Uint8Array(this.dataView.buffer, offset, dataLength);
            offset += dataLength;
            this.properties.palMagic = String.fromCharCode(this.dataView.getUint8(offset), this.dataView.getUint8(offset+1), this.dataView.getUint8(offset+2), this.dataView.getUint8(offset+3));
            offset += 4;
            const palSize = 48;
            this.properties.palData = new Uint8Array(this.dataView.buffer, offset, palSize);
        } else {
            this.version = 1;
            this.properties.width = this.dataView.getUint8(0);
            this.properties.height = this.dataView.getUint8(1);
            offset = 2;
            const cmapLength = this.properties.width * this.properties.height;
            this.properties.cmap = new Uint8Array(this.dataView.buffer, offset, cmapLength);
            offset += cmapLength;
            const bitmapLength = this.properties.width * this.properties.height * 14;
            this.properties.bitmap = new Uint8Array(this.dataView.buffer, offset, bitmapLength);
            offset += bitmapLength;
            this.properties.logo = new Uint8Array(this.dataView.buffer, offset, 70);
        }
    }

    write() {
        let bufferSize = 1024 * 1024; // Oversize
        const outView = new DataView(new ArrayBuffer(bufferSize));
        let offset = 0;
        if (this.version === 2) {
            for (let i = 0; i < 4; i++) outView.setUint8(offset++, this.properties.magic.charCodeAt(i));
            outView.setUint16(offset, this.properties.width, true); offset += 2;
            outView.setUint16(offset, this.properties.height, true); offset += 2;
            for (let byte of this.properties.data) outView.setUint8(offset++, byte);
            for (let i = 0; i < 4; i++) outView.setUint8(offset++, this.properties.palMagic.charCodeAt(i));
            for (let byte of this.properties.palData) outView.setUint8(offset++, byte);
        } else if (this.version === 1) {
            outView.setUint8(offset++, this.properties.width);
            outView.setUint8(offset++, this.properties.height);
            for (let byte of this.properties.cmap) outView.setUint8(offset++, byte);
            for (let byte of this.properties.bitmap) outView.setUint8(offset++, byte);
            for (let byte of this.properties.logo) outView.setUint8(offset++, byte);
        }
        return new Uint8Array(outView.buffer, 0, offset); // Return for save, e.g., Blob
    }

    printProperties() {
        console.log(`Version: ${this.version}`);
        for (const [key, value] of Object.entries(this.properties)) {
            console.log(`${key}: ${value instanceof Uint8Array ? '(binary data, length ' + value.length + ')' : value}`);
        }
    }
}

// Example usage:
// const epa = new EpaFile();
// const file = ... // From input or drop
// await epa.open(file);
// epa.read();
// epa.printProperties();
// const outputData = epa.write();
// // Save as file using Blob

7. C class (using C++ for class support) for .EPA file handling

#include <iostream>
#include <fstream>
#include <vector>
#include <cstring>
#include <cstdint>

class EpaFile {
private:
    int version;
    std::vector<uint8_t> fileData;
    uint8_t width8, height8; // For v1
    uint16_t width16, height16; // For v2
    std::vector<uint8_t> cmap, bitmap, logo, data, palData;
    char magic[5], palMagic[5];

public:
    EpaFile() : version(0) {
        magic[4] = '\0';
        palMagic[4] = '\0';
    }

    bool open(const std::string& filename) {
        std::ifstream file(filename, std::ios::binary | std::ios::ate);
        if (!file) return false;
        std::streamsize size = file.tellg();
        file.seekg(0, std::ios::beg);
        fileData.resize(size);
        file.read(reinterpret_cast<char*>(fileData.data()), size);
        return true;
    }

    void read() {
        if (fileData.size() < 4) return;
        strncpy(magic, reinterpret_cast<char*>(fileData.data()), 4);
        if (strcmp(magic, "AWBM") == 0) {
            version = 2;
            width16 = *reinterpret_cast<uint16_t*>(fileData.data() + 4);
            height16 = *reinterpret_cast<uint16_t*>(fileData.data() + 6);
            size_t offset = 8;
            // Assume 4-bit
            size_t dataLength = (width16 + (7 - width16 % 8)) * height16 / 2;
            data.assign(fileData.begin() + offset, fileData.begin() + offset + dataLength);
            offset += dataLength;
            strncpy(palMagic, reinterpret_cast<char*>(fileData.data() + offset), 4);
            offset += 4;
            size_t palSize = 48;
            palData.assign(fileData.begin() + offset, fileData.begin() + offset + palSize);
        } else {
            version = 1;
            width8 = fileData[0];
            height8 = fileData[1];
            size_t offset = 2;
            size_t cmapLength = width8 * height8;
            cmap.assign(fileData.begin() + offset, fileData.begin() + offset + cmapLength);
            offset += cmapLength;
            size_t bitmapLength = width8 * height8 * 14;
            bitmap.assign(fileData.begin() + offset, fileData.begin() + offset + bitmapLength);
            offset += bitmapLength;
            logo.assign(fileData.begin() + offset, fileData.begin() + offset + 70);
        }
    }

    void write(const std::string& filename) {
        std::ofstream file(filename, std::ios::binary);
        if (!file) return;
        if (version == 2) {
            file.write(magic, 4);
            file.write(reinterpret_cast<const char*>(&width16), 2);
            file.write(reinterpret_cast<const char*>(&height16), 2);
            file.write(reinterpret_cast<const char*>(data.data()), data.size());
            file.write(palMagic, 4);
            file.write(reinterpret_cast<const char*>(palData.data()), palData.size());
        } else if (version == 1) {
            file.write(reinterpret_cast<const char*>(&width8), 1);
            file.write(reinterpret_cast<const char*>(&height8), 1);
            file.write(reinterpret_cast<const char*>(cmap.data()), cmap.size());
            file.write(reinterpret_cast<const char*>(bitmap.data()), bitmap.size());
            file.write(reinterpret_cast<const char*>(logo.data()), logo.size());
        }
    }

    void printProperties() {
        std::cout << "Version: " << version << std::endl;
        if (version == 2) {
            std::cout << "Magic: " << magic << std::endl;
            std::cout << "Width: " << width16 << std::endl;
            std::cout << "Height: " << height16 << std::endl;
            std::cout << "Data: (binary data, length " << data.size() << ")" << std::endl;
            std::cout << "PalMagic: " << palMagic << std::endl;
            std::cout << "PalData: (binary data, length " << palData.size() << ")" << std::endl;
        } else {
            std::cout << "Width: " << static_cast<int>(width8) << std::endl;
            std::cout << "Height: " << static_cast<int>(height8) << std::endl;
            std::cout << "CMap: (binary data, length " << cmap.size() << ")" << std::endl;
            std::cout << "Bitmap: (binary data, length " << bitmap.size() << ")" << std::endl;
            std::cout << "Logo: (binary data, length " << logo.size() << ")" << std::endl;
        }
    }
};

// Example usage:
// EpaFile epa;
// epa.open("example.epa");
// epa.read();
// epa.printProperties();
// epa.write("output.epa");