Task 509: .PAL File Format

Task 509: .PAL File Format

File Format Specifications for .PAL

The .PAL file format refers to the Microsoft RIFF (Resource Interchange File Format) Palette file, a binary format used to store color palettes. It is a chunk-based structure in little-endian byte order, typically containing a single "data" chunk with the palette information. The format supports simple palettes (RGB colors) and can include optional metadata chunks, but the core is the RIFF header followed by the data chunk. Chunks are aligned to even boundaries, with padding (not counted in chunk size) if the data size is odd.

1. List of All Properties Intrinsic to This File Format

The properties (fields) define the structure of the file. They include headers, sizes, and the palette data itself. Here is the complete list based on the format specification:

  • RIFF Signature: 4-byte string, always "RIFF" (0x52 49 46 46).
  • File Size: 4-byte unsigned integer (uint32, little-endian), representing the size of the file contents after the signature (total file size minus 8 bytes).
  • Form Type: 4-byte string, always "PAL " (0x50 41 4C 20, with trailing space).
  • Chunk ID: 4-byte string for the main chunk, typically "data" (0x64 61 74 61).
  • Chunk Size: 4-byte unsigned integer (uint32, little-endian), size of the chunk data (excluding header and padding).
  • Palette Version: 2-byte unsigned integer (uint16, little-endian), typically 0x0300 (768 in decimal) for Windows-compatible palettes.
  • Number of Palette Entries: 2-byte unsigned integer (uint16, little-endian), the count of colors in the palette (e.g., 256).
  • Palette Entries: Array of entries (number specified above), each 4 bytes:
  • Red Component: 1-byte unsigned integer (uint8, 0-255).
  • Green Component: 1-byte unsigned integer (uint8, 0-255).
  • Blue Component: 1-byte unsigned integer (uint8, 0-255).
  • Flags: 1-byte unsigned integer (uint8), palette flags (e.g., 0x00 for normal, 0x01 for reserved; often ignored for basic use).

Optional properties (not always present, as they are in additional chunks like "ISFT" for software info):

  • Other Chunk IDs and Data: Variable, e.g., metadata chunks with their own ID, size, and data.

3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .PAL File Dump

Here is a standalone HTML page with embedded JavaScript that allows drag-and-drop of a .PAL file. It parses the file (assuming simple RIFF structure with "data" chunk) and dumps all properties to the screen. Save this as an HTML file and open in a browser.

.PAL File Parser
Drag and drop a .PAL file here

4. Python Class for .PAL File Handling

import struct
import sys

class PalFile:
    def __init__(self, filename=None):
        self.riff_sig = None
        self.file_size = None
        self.form_type = None
        self.chunk_id = None
        self.chunk_size = None
        self.pal_version = None
        self.num_entries = None
        self.entries = []  # list of (r, g, b, flags)
        if filename:
            self.read(filename)

    def read(self, filename):
        with open(filename, 'rb') as f:
            data = f.read()
        offset = 0
        self.riff_sig = data[offset:offset+4].decode('ascii')
        offset += 4
        self.file_size = struct.unpack_from('<I', data, offset)[0]
        offset += 4
        self.form_type = data[offset:offset+4].decode('ascii')
        offset += 4
        self.chunk_id = data[offset:offset+4].decode('ascii')
        offset += 4
        self.chunk_size = struct.unpack_from('<I', data, offset)[0]
        offset += 4
        self.pal_version = struct.unpack_from('<H', data, offset)[0]
        offset += 2
        self.num_entries = struct.unpack_from('<H', data, offset)[0]
        offset += 2
        self.entries = []
        for _ in range(self.num_entries):
            r, g, b, flags = struct.unpack_from('<BBBB', data, offset)
            self.entries.append((r, g, b, flags))
            offset += 4

    def print_properties(self):
        print(f"RIFF Signature: {self.riff_sig}")
        print(f"File Size: {self.file_size}")
        print(f"Form Type: {self.form_type}")
        print(f"Chunk ID: {self.chunk_id}")
        print(f"Chunk Size: {self.chunk_size}")
        print(f"Palette Version: 0x{self.pal_version:04X} ({self.pal_version})")
        print(f"Number of Palette Entries: {self.num_entries}")
        print("Palette Entries:")
        for i, (r, g, b, flags) in enumerate(self.entries):
            print(f"Entry {i}: R={r}, G={g}, B={b}, Flags=0x{flags:02X}")

    def write(self, filename):
        data = bytearray()
        data.extend(self.riff_sig.encode('ascii'))
        data.extend(struct.pack('<I', self.file_size))
        data.extend(self.form_type.encode('ascii'))
        data.extend(self.chunk_id.encode('ascii'))
        data.extend(struct.pack('<I', self.chunk_size))
        data.extend(struct.pack('<H', self.pal_version))
        data.extend(struct.pack('<H', self.num_entries))
        for r, g, b, flags in self.entries:
            data.extend(struct.pack('<BBBB', r, g, b, flags))
        # Update sizes if needed (for simplicity, assume pre-calculated)
        with open(filename, 'wb') as f:
            f.write(data)

# Example usage: python script.py input.pal
if __name__ == "__main__":
    if len(sys.argv) > 1:
        pal = PalFile(sys.argv[1])
        pal.print_properties()

To write, set properties manually and call write(output.pal). Note: For write, you must set file_size and chunk_size appropriately (e.g., chunk_size = 4 + num_entries * 4; file_size = chunk_size + 20).

5. Java Class for .PAL File Handling

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

public class PalFile {
    private String riffSig;
    private int fileSize;
    private String formType;
    private String chunkId;
    private int chunkSize;
    private short palVersion;
    private short numEntries;
    private int[][] entries;  // [numEntries][4] {r, g, b, flags}

    public PalFile(String filename) throws IOException {
        read(filename);
    }

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

        byte[] sigBytes = new byte[4];
        bb.position(offset);
        bb.get(sigBytes);
        riffSig = new String(sigBytes);
        offset += 4;

        fileSize = bb.getInt(offset);
        offset += 4;

        bb.position(offset);
        bb.get(sigBytes);
        formType = new String(sigBytes);
        offset += 4;

        bb.position(offset);
        bb.get(sigBytes);
        chunkId = new String(sigBytes);
        offset += 4;

        chunkSize = bb.getInt(offset);
        offset += 4;

        palVersion = bb.getShort(offset);
        offset += 2;

        numEntries = bb.getShort(offset);
        offset += 2;

        entries = new int[numEntries][4];
        for (int i = 0; i < numEntries; i++) {
            entries[i][0] = bb.get(offset++) & 0xFF;  // r
            entries[i][1] = bb.get(offset++) & 0xFF;  // g
            entries[i][2] = bb.get(offset++) & 0xFF;  // b
            entries[i][3] = bb.get(offset++) & 0xFF;  // flags
        }
    }

    public void printProperties() {
        System.out.println("RIFF Signature: " + riffSig);
        System.out.println("File Size: " + fileSize);
        System.out.println("Form Type: " + formType);
        System.out.println("Chunk ID: " + chunkId);
        System.out.println("Chunk Size: " + chunkSize);
        System.out.printf("Palette Version: 0x%04X (%d)\n", palVersion, palVersion);
        System.out.println("Number of Palette Entries: " + numEntries);
        System.out.println("Palette Entries:");
        for (int i = 0; i < numEntries; i++) {
            System.out.printf("Entry %d: R=%d, G=%d, B=%d, Flags=0x%02X\n",
                    i, entries[i][0], entries[i][1], entries[i][2], entries[i][3]);
        }
    }

    public void write(String filename) throws IOException {
        ByteBuffer bb = ByteBuffer.allocate(12 + 8 + 4 + numEntries * 4).order(ByteOrder.LITTLE_ENDIAN);
        bb.put(riffSig.getBytes());
        bb.putInt(fileSize);
        bb.put(formType.getBytes());
        bb.put(chunkId.getBytes());
        bb.putInt(chunkSize);
        bb.putShort(palVersion);
        bb.putShort(numEntries);
        for (int[] entry : entries) {
            bb.put((byte) entry[0]);
            bb.put((byte) entry[1]);
            bb.put((byte) entry[2]);
            bb.put((byte) entry[3]);
        }
        try (FileOutputStream fos = new FileOutputStream(filename)) {
            fos.write(bb.array());
        }
    }

    // Example usage: java PalFile input.pal
    public static void main(String[] args) throws IOException {
        if (args.length > 0) {
            PalFile pal = new PalFile(args[0]);
            pal.printProperties();
        }
    }
}

To write, set properties and call write(output.pal). Update fileSize and chunkSize as in Python example.

6. JavaScript Class for .PAL File Handling

class PalFile {
    constructor(buffer = null) {
        this.riffSig = null;
        this.fileSize = null;
        this.formType = null;
        this.chunkId = null;
        this.chunkSize = null;
        this.palVersion = null;
        this.numEntries = null;
        this.entries = [];  // array of {r, g, b, flags}
        if (buffer) {
            this.read(buffer);
        }
    }

    read(buffer) {
        const dv = new DataView(buffer);
        let offset = 0;
        this.riffSig = String.fromCharCode(dv.getUint8(offset++), dv.getUint8(offset++), dv.getUint8(offset++), dv.getUint8(offset++));
        this.fileSize = dv.getUint32(offset, true);
        offset += 4;
        this.formType = String.fromCharCode(dv.getUint8(offset++), dv.getUint8(offset++), dv.getUint8(offset++), dv.getUint8(offset++));
        this.chunkId = String.fromCharCode(dv.getUint8(offset++), dv.getUint8(offset++), dv.getUint8(offset++), dv.getUint8(offset++));
        this.chunkSize = dv.getUint32(offset, true);
        offset += 4;
        this.palVersion = dv.getUint16(offset, true);
        offset += 2;
        this.numEntries = dv.getUint16(offset, true);
        offset += 2;
        this.entries = [];
        for (let i = 0; i < this.numEntries; i++) {
            const r = dv.getUint8(offset++);
            const g = dv.getUint8(offset++);
            const b = dv.getUint8(offset++);
            const flags = dv.getUint8(offset++);
            this.entries.push({r, g, b, flags});
        }
    }

    printProperties() {
        console.log(`RIFF Signature: ${this.riffSig}`);
        console.log(`File Size: ${this.fileSize}`);
        console.log(`Form Type: ${this.formType}`);
        console.log(`Chunk ID: ${this.chunkId}`);
        console.log(`Chunk Size: ${this.chunkSize}`);
        console.log(`Palette Version: 0x${this.palVersion.toString(16).toUpperCase()} (${this.palVersion})`);
        console.log(`Number of Palette Entries: ${this.numEntries}`);
        console.log('Palette Entries:');
        this.entries.forEach((entry, i) => {
            console.log(`Entry ${i}: R=${entry.r}, G=${entry.g}, B=${entry.b}, Flags=0x${entry.flags.toString(16).toUpperCase()}`);
        });
    }

    write() {
        const buffer = new ArrayBuffer(12 + 8 + 4 + this.numEntries * 4);
        const dv = new DataView(buffer);
        let offset = 0;
        for (let char of this.riffSig) dv.setUint8(offset++, char.charCodeAt(0));
        dv.setUint32(offset, this.fileSize, true); offset += 4;
        for (let char of this.formType) dv.setUint8(offset++, char.charCodeAt(0));
        for (let char of this.chunkId) dv.setUint8(offset++, char.charCodeAt(0));
        dv.setUint32(offset, this.chunkSize, true); offset += 4;
        dv.setUint16(offset, this.palVersion, true); offset += 2;
        dv.setUint16(offset, this.numEntries, true); offset += 2;
        this.entries.forEach(entry => {
            dv.setUint8(offset++, entry.r);
            dv.setUint8(offset++, entry.g);
            dv.setUint8(offset++, entry.b);
            dv.setUint8(offset++, entry.flags);
        });
        return buffer;  // Return ArrayBuffer; use Blob to save
    }
}

// Example usage in Node.js: node script.js input.pal
const fs = require('fs');
if (process.argv.length > 2) {
    const buffer = fs.readFileSync(process.argv[2]).buffer;
    const pal = new PalFile(buffer);
    pal.printProperties();
}

To write, call write() to get buffer, then save (e.g., via fs in Node). Update sizes as needed.

7. C Class (C++ Implementation) for .PAL File Handling

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

struct PaletteEntry {
    uint8_t r, g, b, flags;
};

class PalFile {
public:
    char riffSig[5];
    uint32_t fileSize;
    char formType[5];
    char chunkId[5];
    uint32_t chunkSize;
    uint16_t palVersion;
    uint16_t numEntries;
    std::vector<PaletteEntry> entries;

    PalFile(const std::string& filename) {
        read(filename);
    }

    void read(const std::string& filename) {
        std::ifstream file(filename, std::ios::binary | std::ios::ate);
        if (!file) {
            std::cerr << "Error opening file" << std::endl;
            return;
        }
        size_t size = file.tellg();
        file.seekg(0);
        std::vector<char> data(size);
        file.read(data.data(), size);

        size_t offset = 0;
        memcpy(riffSig, data.data() + offset, 4);
        riffSig[4] = '\0';
        offset += 4;

        memcpy(&fileSize, data.data() + offset, 4);
        #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
        fileSize = __builtin_bswap32(fileSize);
        #endif
        offset += 4;

        memcpy(formType, data.data() + offset, 4);
        formType[4] = '\0';
        offset += 4;

        memcpy(chunkId, data.data() + offset, 4);
        chunkId[4] = '\0';
        offset += 4;

        memcpy(&chunkSize, data.data() + offset, 4);
        #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
        chunkSize = __builtin_bswap32(chunkSize);
        #endif
        offset += 4;

        memcpy(&palVersion, data.data() + offset, 2);
        #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
        palVersion = __builtin_bswap16(palVersion);
        #endif
        offset += 2;

        memcpy(&numEntries, data.data() + offset, 2);
        #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
        numEntries = __builtin_bswap16(numEntries);
        #endif
        offset += 2;

        entries.resize(numEntries);
        for (uint16_t i = 0; i < numEntries; ++i) {
            entries[i].r = static_cast<uint8_t>(data[offset++]);
            entries[i].g = static_cast<uint8_t>(data[offset++]);
            entries[i].b = static_cast<uint8_t>(data[offset++]);
            entries[i].flags = static_cast<uint8_t>(data[offset++]);
        }
    }

    void printProperties() const {
        std::cout << "RIFF Signature: " << riffSig << std::endl;
        std::cout << "File Size: " << fileSize << std::endl;
        std::cout << "Form Type: " << formType << std::endl;
        std::cout << "Chunk ID: " << chunkId << std::endl;
        std::cout << "Chunk Size: " << chunkSize << std::endl;
        std::cout << "Palette Version: 0x" << std::hex << palVersion << " (" << std::dec << palVersion << ")" << std::endl;
        std::cout << "Number of Palette Entries: " << numEntries << std::endl;
        std::cout << "Palette Entries:" << std::endl;
        for (uint16_t i = 0; i < numEntries; ++i) {
            std::cout << "Entry " << i << ": R=" << static_cast<int>(entries[i].r)
                      << ", G=" << static_cast<int>(entries[i].g)
                      << ", B=" << static_cast<int>(entries[i].b)
                      << ", Flags=0x" << std::hex << static_cast<int>(entries[i].flags) << std::dec << std::endl;
        }
    }

    void write(const std::string& filename) const {
        std::ofstream file(filename, std::ios::binary);
        if (!file) {
            std::cerr << "Error opening file for write" << std::endl;
            return;
        }
        file.write(riffSig, 4);
        uint32_t leFileSize = fileSize;
        #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
        leFileSize = __builtin_bswap32(leFileSize);
        #endif
        file.write(reinterpret_cast<const char*>(&leFileSize), 4);
        file.write(formType, 4);
        file.write(chunkId, 4);
        uint32_t leChunkSize = chunkSize;
        #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
        leChunkSize = __builtin_bswap32(leChunkSize);
        #endif
        file.write(reinterpret_cast<const char*>(&leChunkSize), 4);
        uint16_t lePalVersion = palVersion;
        #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
        lePalVersion = __builtin_bswap16(lePalVersion);
        #endif
        file.write(reinterpret_cast<const char*>(&lePalVersion), 2);
        uint16_t leNumEntries = numEntries;
        #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
        leNumEntries = __builtin_bswap16(leNumEntries);
        #endif
        file.write(reinterpret_cast<const char*>(&leNumEntries), 2);
        for (const auto& entry : entries) {
            file.put(static_cast<char>(entry.r));
            file.put(static_cast<char>(entry.g));
            file.put(static_cast<char>(entry.b));
            file.put(static_cast<char>(entry.flags));
        }
    }
};

// Example usage: ./program input.pal
int main(int argc, char* argv[]) {
    if (argc > 1) {
        PalFile pal(argv[1]);
        pal.printProperties();
    }
    return 0;
}

Compile with g++ (handles little-endian write). To write, set properties and call write(output.pal). Update sizes as needed.