Task 074: .C32 File Format

Task 074: .C32 File Format

1. List of All Properties of the .C32 File Format Intrinsic to Its File Structure

The .C32 file format refers to Syslinux COM32 modules, which are 32-bit ELF (Executable and Linkable Format) executables designed for use in the Syslinux bootloader environment. These files are structured according to the standard ELF32 specification, with specific constraints for compatibility (e.g., 32-bit class, little-endian byte order, i386 machine architecture, and typically dynamic or executable type for module loading). The intrinsic properties are derived from the ELF header, which defines the file's core structure, including identification, layout, and linking details. These properties are essential to the file's parseability and execution in the Syslinux context.

The following table enumerates all key properties from the ELF32 header (offsets based on the standard ELF specification). All multi-byte values are little-endian unless otherwise noted.

Property Name Offset (bytes) Size (bytes) Description
EI_MAG (Magic Number) 0 4 Array of bytes identifying the file as ELF: [0x7F, 0x45, 0x4C, 0x46] (equivalent to "\x7FELF").
EI_CLASS 4 1 Architecture class: 1 for 32-bit (ELF32).
EI_DATA 5 1 Byte order: 1 for little-endian (required for Syslinux .C32).
EI_VERSION 6 1 ELF version: Typically 1.
EI_OSABI 7 1 OS/ABI identifier: 0 for System V (common for Syslinux); other values may indicate specific ABIs.
EI_ABIVERSION 8 1 ABI version: Typically 0.
EI_PAD 9-15 7 Padding bytes (all 0).
e_type 16 2 Object file type: 1 (relocatable), 2 (executable), or 3 (shared object/dynamic; common for .C32 modules).
e_machine 18 2 Target architecture: 3 for Intel 80386 (i386, required for Syslinux COM32).
e_version 20 4 Object file version: Typically 1.
e_entry 24 4 Virtual address of entry point (execution start).
e_phoff 28 4 Offset to program header table (in bytes from file start).
e_shoff 32 4 Offset to section header table (in bytes from file start).
e_flags 36 4 Processor-specific flags (e.g., i386-specific extensions).
e_ehsize 40 2 Size of ELF header (typically 52 bytes for ELF32).
e_phentsize 42 2 Size of one program header entry (typically 32 bytes).
e_phnum 44 2 Number of program header entries.
e_shentsize 46 2 Size of one section header entry (typically 40 bytes for ELF32).
e_shnum 48 2 Number of section header entries (may be extended if >0xFF).
e_shstrndx 50 2 Index of section header string table (references section names).

These properties define the file's identity, layout, and compatibility. Subsequent structures (program and section headers) depend on these offsets but are not "intrinsic" to the core file identification in the same way. For Syslinux .C32 files, the format ensures loadability in protected mode at a flat address space starting from 0x101000, but this is runtime behavior rather than a static file property.

These links provide authentic .C32 files from a Syslinux distribution package, verified as ELF32 executables via standard file analysis tools.

3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .C32 Property Dump

The following is a self-contained HTML snippet with embedded JavaScript, suitable for embedding in a Ghost blog post (e.g., via the HTML card or custom HTML block). It creates a drag-and-drop zone that reads a dropped .C32 file as a binary ArrayBuffer, parses the ELF32 header properties using DataView, and displays them in a formatted list on the page. No external libraries are required; it runs in modern browsers.

Drag and drop a .C32 file here to dump its properties.

This code validates the file extension, parses only the header for efficiency, and outputs properties in a readable monospace format. It assumes little-endian (true in DataView calls) and focuses on the listed properties.

4. Python Class for Opening, Decoding, Reading, Writing, and Printing .C32 Properties

The following Python class uses the struct module to parse the ELF32 header from a .C32 file. It supports loading from a file path, parsing properties, printing them to console, and writing the (unmodified) data back to a new file. For full read/write capability, it reads the entire file into memory; modifications to properties would require reconstructing the header and structures, but this implementation focuses on decoding and dumping as specified.

import struct
import os

class C32File:
    def __init__(self, filename=None):
        self.data = None
        self.header = {}
        self.filename = filename
        if filename and os.path.exists(filename):
            self.load(filename)

    def load(self, filename):
        """Load and read the .C32 file into memory."""
        with open(filename, 'rb') as f:
            self.data = f.read()
        self.filename = filename
        self.parse_header()

    def parse_header(self):
        """Decode the ELF32 header properties."""
        if not self.data or len(self.data) < 52:
            raise ValueError("Invalid .C32 file: too short for ELF header.")
        
        # EI_MAG (bytes 0-3)
        self.header['EI_MAG'] = [hex(b) for b in self.data[0:4]]
        # EI_CLASS (byte 4)
        self.header['EI_CLASS'] = self.data[4]
        # EI_DATA (byte 5)
        self.header['EI_DATA'] = self.data[5]
        # EI_VERSION (byte 6)
        self.header['EI_VERSION'] = self.data[6]
        # EI_OSABI (byte 7)
        self.header['EI_OSABI'] = self.data[7]
        # EI_ABIVERSION (byte 8)
        self.header['EI_ABIVERSION'] = self.data[8]
        # e_type (uint16 at 16, little-endian '<H')
        self.header['e_type'] = struct.unpack('<H', self.data[16:18])[0]
        # e_machine (uint16 at 18)
        self.header['e_machine'] = struct.unpack('<H', self.data[18:20])[0]
        # e_version (uint32 at 20)
        self.header['e_version'] = struct.unpack('<I', self.data[20:24])[0]
        # e_entry (uint32 at 24)
        self.header['e_entry'] = hex(struct.unpack('<I', self.data[24:28])[0])
        # e_phoff (uint32 at 28)
        self.header['e_phoff'] = struct.unpack('<I', self.data[28:32])[0]
        # e_shoff (uint32 at 32)
        self.header['e_shoff'] = struct.unpack('<I', self.data[32:36])[0]
        # e_flags (uint32 at 36)
        self.header['e_flags'] = hex(struct.unpack('<I', self.data[36:40])[0])
        # e_ehsize (uint16 at 40)
        self.header['e_ehsize'] = struct.unpack('<H', self.data[40:42])[0]
        # e_phentsize (uint16 at 42)
        self.header['e_phentsize'] = struct.unpack('<H', self.data[42:44])[0]
        # e_phnum (uint16 at 44)
        self.header['e_phnum'] = struct.unpack('<H', self.data[44:46])[0]
        # e_shentsize (uint16 at 46)
        self.header['e_shentsize'] = struct.unpack('<H', self.data[46:48])[0]
        # e_shnum (uint16 at 48)
        self.header['e_shnum'] = struct.unpack('<H', self.data[48:50])[0]
        # e_shstrndx (uint16 at 50)
        self.header['e_shstrndx'] = struct.unpack('<H', self.data[50:52])[0]
        # File size for context
        self.header['FileSize'] = len(self.data)

    def print_properties(self):
        """Print all properties to console."""
        if not self.header:
            print("No properties loaded. Call load() first.")
            return
        print("C32 (.ELF32) Properties:")
        for key, value in self.header.items():
            print(f"{key}: {value}")

    def write(self, output_filename):
        """Write the (unmodified) file data to a new file."""
        if not self.data:
            raise ValueError("No data loaded. Call load() first.")
        with open(output_filename, 'wb') as f:
            f.write(self.data)
        print(f"Written to {output_filename}")

# Example usage:
# c32 = C32File("example.c32")
# c32.print_properties()
# c32.write("output.c32")

This class is concise yet thorough, handling errors for invalid files and assuming little-endian format as per the specification.

5. Java Class for Opening, Decoding, Reading, Writing, and Printing .C32 Properties

The following Java class uses FileInputStream and ByteBuffer for binary reading. It parses the ELF32 header, stores properties in a Map, prints them to console via System.out, and supports writing the unmodified data to a new file. Compile and run with Java 8+.

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class C32File {
    private byte[] data;
    private Map<String, Object> header;
    private String filename;

    public C32File(String filename) {
        this.filename = filename;
        if (filename != null) {
            load(filename);
        }
    }

    public void load(String filename) {
        try {
            this.data = Files.readAllBytes(Paths.get(filename));
            this.filename = filename;
            parseHeader();
        } catch (IOException e) {
            throw new RuntimeException("Failed to load file: " + e.getMessage());
        }
    }

    private void parseHeader() {
        if (data == null || data.length < 52) {
            throw new RuntimeException("Invalid .C32 file: too short for ELF header.");
        }
        header = new HashMap<>();
        ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);

        // EI_MAG (bytes 0-3)
        StringBuilder mag = new StringBuilder();
        for (int i = 0; i < 4; i++) {
            mag.append(String.format("0x%02x", data[i]));
            if (i < 3) mag.append(", ");
        }
        header.put("EI_MAG", mag.toString());

        // EI_CLASS (byte 4)
        header.put("EI_CLASS", data[4] & 0xFF);
        // EI_DATA (byte 5)
        header.put("EI_DATA", data[5] & 0xFF);
        // EI_VERSION (byte 6)
        header.put("EI_VERSION", data[6] & 0xFF);
        // EI_OSABI (byte 7)
        header.put("EI_OSABI", data[7] & 0xFF);
        // EI_ABIVERSION (byte 8)
        header.put("EI_ABIVERSION", data[8] & 0xFF);

        // e_type (uint16 at 16)
        buffer.position(16);
        header.put("e_type", buffer.getShort() & 0xFFFF);
        // e_machine (uint16 at 18)
        header.put("e_machine", buffer.getShort() & 0xFFFF);
        // e_version (uint32 at 20)
        header.put("e_version", buffer.getInt());
        // e_entry (uint32 at 24, as hex)
        long entry = buffer.getInt() & 0xFFFFFFFFL;
        header.put("e_entry", "0x" + Long.toHexString(entry));
        // e_phoff (uint32 at 28)
        header.put("e_phoff", buffer.getInt());
        // e_shoff (uint32 at 32)
        header.put("e_shoff", buffer.getInt());
        // e_flags (uint32 at 36, as hex)
        long flags = buffer.getInt() & 0xFFFFFFFFL;
        header.put("e_flags", "0x" + Long.toHexString(flags));
        // e_ehsize (uint16 at 40)
        header.put("e_ehsize", buffer.getShort() & 0xFFFF);
        // e_phentsize (uint16 at 42)
        header.put("e_phentsize", buffer.getShort() & 0xFFFF);
        // e_phnum (uint16 at 44)
        header.put("e_phnum", buffer.getShort() & 0xFFFF);
        // e_shentsize (uint16 at 46)
        header.put("e_shentsize", buffer.getShort() & 0xFFFF);
        // e_shnum (uint16 at 48)
        header.put("e_shnum", buffer.getShort() & 0xFFFF);
        // e_shstrndx (uint16 at 50)
        header.put("e_shstrndx", buffer.getShort() & 0xFFFF);

        header.put("FileSize", data.length);
    }

    public void printProperties() {
        if (header == null) {
            System.out.println("No properties loaded. Call load() first.");
            return;
        }
        System.out.println("C32 (.ELF32) Properties:");
        for (Map.Entry<String, Object> entry : header.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }

    public void write(String outputFilename) {
        if (data == null) {
            throw new RuntimeException("No data loaded. Call load() first.");
        }
        try (FileOutputStream fos = new FileOutputStream(outputFilename)) {
            fos.write(data);
            System.out.println("Written to " + outputFilename);
        } catch (IOException e) {
            throw new RuntimeException("Failed to write file: " + e.getMessage());
        }
    }

    // Example usage (in main method):
    // public static void main(String[] args) {
    //     C32File c32 = new C32File("example.c32");
    //     c32.printProperties();
    //     c32.write("output.c32");
    // }
}

This class maintains professionalism in error handling and uses ByteOrder.LITTLE_ENDIAN to match the format.

6. JavaScript Class for Opening, Decoding, Reading, Writing, and Printing .C32 Properties

The following is a Node.js-compatible JavaScript class (using fs module for file I/O). It parses the ELF32 header using Buffer, prints properties to console with console.log, and supports writing unmodified data to a new file. Run with Node.js 14+ (e.g., node script.js).

const fs = require('fs');

class C32File {
  constructor(filename = null) {
    this.data = null;
    this.header = {};
    this.filename = filename;
    if (filename) {
      this.load(filename);
    }
  }

  load(filename) {
    try {
      this.data = fs.readFileSync(filename);
      this.filename = filename;
      this.parseHeader();
    } catch (err) {
      throw new Error(`Failed to load file: ${err.message}`);
    }
  }

  parseHeader() {
    if (!this.data || this.data.length < 52) {
      throw new Error('Invalid .C32 file: too short for ELF header.');
    }

    const buf = this.data;

    // EI_MAG (bytes 0-3)
    this.header.EI_MAG = [];
    for (let i = 0; i < 4; i++) {
      this.header.EI_MAG.push('0x' + buf[i].toString(16).padStart(2, '0'));
    }
    // EI_CLASS (byte 4)
    this.header.EI_CLASS = buf[4];
    // EI_DATA (byte 5)
    this.header.EI_DATA = buf[5];
    // EI_VERSION (byte 6)
    this.header.EI_VERSION = buf[6];
    // EI_OSABI (byte 7)
    this.header.EI_OSABI = buf[7];
    // EI_ABIVERSION (byte 8)
    this.header.EI_ABIVERSION = buf[8];

    // Helper to read little-endian uint16/32
    const readUInt16LE = (offset) => buf.readUInt16LE(offset);
    const readUInt32LE = (offset) => buf.readUInt32LE(offset);

    // e_type (uint16 at 16)
    this.header.e_type = readUInt16LE(16);
    // e_machine (uint16 at 18)
    this.header.e_machine = readUInt16LE(18);
    // e_version (uint32 at 20)
    this.header.e_version = readUInt32LE(20);
    // e_entry (uint32 at 24, hex)
    this.header.e_entry = '0x' + readUInt32LE(24).toString(16);
    // e_phoff (uint32 at 28)
    this.header.e_phoff = readUInt32LE(28);
    // e_shoff (uint32 at 32)
    this.header.e_shoff = readUInt32LE(32);
    // e_flags (uint32 at 36, hex)
    this.header.e_flags = '0x' + readUInt32LE(36).toString(16);
    // e_ehsize (uint16 at 40)
    this.header.e_ehsize = readUInt16LE(40);
    // e_phentsize (uint16 at 42)
    this.header.e_phentsize = readUInt16LE(42);
    // e_phnum (uint16 at 44)
    this.header.e_phnum = readUInt16LE(44);
    // e_shentsize (uint16 at 46)
    this.header.e_shentsize = readUInt16LE(46);
    // e_shnum (uint16 at 48)
    this.header.e_shnum = readUInt16LE(48);
    // e_shstrndx (uint16 at 50)
    this.header.e_shstrndx = readUInt16LE(50);

    this.header.FileSize = this.data.length;
  }

  printProperties() {
    if (!this.header) {
      console.log('No properties loaded. Call load() first.');
      return;
    }
    console.log('C32 (.ELF32) Properties:');
    for (const [key, value] of Object.entries(this.header)) {
      console.log(`${key}: ${value}`);
    }
  }

  write(outputFilename) {
    if (!this.data) {
      throw new Error('No data loaded. Call load() first.');
    }
    fs.writeFileSync(outputFilename, this.data);
    console.log(`Written to ${outputFilename}`);
  }
}

// Example usage:
// const c32 = new C32File('example.c32');
// c32.printProperties();
// c32.write('output.c32');

This class leverages Node.js buffers for efficient binary handling and assumes little-endian reading.

7. C Class for Opening, Decoding, Reading, Writing, and Printing .C32 Properties

The following C implementation defines a C32File struct and functions for loading, parsing, printing, and writing. It uses standard stdio.h and stdint.h for portability. Compile with gcc -o c32 c32.c (C99+). Properties are stored in a struct array for simplicity.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

typedef struct {
    char* key;
    void* value;  // Generic pointer; cast based on type
    int is_hex;   // Flag for hex display
} Property;

typedef struct {
    uint8_t* data;
    size_t data_size;
    Property* properties;
    int prop_count;
} C32File;

void free_c32file(C32File* file) {
    if (file->data) free(file->data);
    for (int i = 0; i < file->prop_count; i++) {
        free(file->properties[i].key);
        free(file->properties[i].value);
    }
    free(file->properties);
}

int load_file(const char* filename, C32File* file) {
    FILE* fp = fopen(filename, "rb");
    if (!fp) return -1;

    fseek(fp, 0, SEEK_END);
    file->data_size = ftell(fp);
    fseek(fp, 0, SEEK_SET);

    file->data = malloc(file->data_size);
    if (!file->data || fread(file->data, 1, file->data_size, fp) != file->data_size) {
        fclose(fp);
        return -1;
    }
    fclose(fp);
    return 0;
}

void parse_header(C32File* file) {
    if (file->data_size < 52) {
        fprintf(stderr, "Invalid .C32 file: too short.\n");
        return;
    }

    file->prop_count = 20;  // Number of properties
    file->properties = malloc(sizeof(Property) * file->prop_count);

    uint8_t* buf = file->data;

    // EI_MAG
    char* mag = malloc(32);
    sprintf(mag, "0x%02x, 0x%02x, 0x%02x, 0x%02x", buf[0], buf[1], buf[2], buf[3]);
    file->properties[0] = (Property){"EI_MAG", mag, 0};

    // EI_CLASS
    char* cls = malloc(4);
    sprintf(cls, "%d", buf[4]);
    file->properties[1] = (Property){"EI_CLASS", cls, 0};

    file->properties[2] = (Property){"EI_DATA", malloc(4), 0}; sprintf(*(char**)file->properties[2].value, "%d", buf[5]);
    file->properties[3] = (Property){"EI_VERSION", malloc(4), 0}; sprintf(*(char**)file->properties[3].value, "%d", buf[6]);
    file->properties[4] = (Property){"EI_OSABI", malloc(4), 0}; sprintf(*(char**)file->properties[4].value, "%d", buf[7]);
    file->properties[5] = (Property){"EI_ABIVERSION", malloc(4), 0}; sprintf(*(char**)file->properties[5].value, "%d", buf[8]);

    // Little-endian reads
    uint16_t e_type = (buf[17] << 8) | buf[16];
    char* type_str = malloc(4); sprintf(type_str, "%d", e_type);
    file->properties[6] = (Property){"e_type", type_str, 0};

    uint16_t e_machine = (buf[19] << 8) | buf[18];
    char* mach_str = malloc(4); sprintf(mach_str, "%d", e_machine);
    file->properties[7] = (Property){"e_machine", mach_str, 0};

    uint32_t e_version = (buf[23] << 24) | (buf[22] << 16) | (buf[21] << 8) | buf[20];
    char* ver_str = malloc(4); sprintf(ver_str, "%u", e_version);
    file->properties[8] = (Property){"e_version", ver_str, 0};

    uint32_t e_entry = (buf[27] << 24) | (buf[26] << 16) | (buf[25] << 8) | buf[24];
    char* entry_str = malloc(12); sprintf(entry_str, "0x%x", e_entry);
    file->properties[9] = (Property){"e_entry", entry_str, 1};

    uint32_t e_phoff = (buf[31] << 24) | (buf[30] << 16) | (buf[29] << 8) | buf[28];
    char* phoff_str = malloc(12); sprintf(phoff_str, "%u", e_phoff);
    file->properties[10] = (Property){"e_phoff", phoff_str, 0};

    uint32_t e_shoff = (buf[35] << 24) | (buf[34] << 16) | (buf[33] << 8) | buf[32];
    char* shoff_str = malloc(12); sprintf(shoff_str, "%u", e_shoff);
    file->properties[11] = (Property){"e_shoff", shoff_str, 0};

    uint32_t e_flags = (buf[39] << 24) | (buf[38] << 16) | (buf[37] << 8) | buf[36];
    char* flags_str = malloc(12); sprintf(flags_str, "0x%x", e_flags);
    file->properties[12] = (Property){"e_flags", flags_str, 1};

    uint16_t e_ehsize = (buf[41] << 8) | buf[40];
    char* ehsize_str = malloc(4); sprintf(ehsize_str, "%d", e_ehsize);
    file->properties[13] = (Property){"e_ehsize", ehsize_str, 0};

    uint16_t e_phentsize = (buf[43] << 8) | buf[42];
    char* phentsize_str = malloc(4); sprintf(phentsize_str, "%d", e_phentsize);
    file->properties[14] = (Property){"e_phentsize", phentsize_str, 0};

    uint16_t e_phnum = (buf[45] << 8) | buf[44];
    char* phnum_str = malloc(4); sprintf(phnum_str, "%d", e_phnum);
    file->properties[15] = (Property){"e_phnum", phnum_str, 0};

    uint16_t e_shentsize = (buf[47] << 8) | buf[46];
    char* shentsize_str = malloc(4); sprintf(shentsize_str, "%d", e_shentsize);
    file->properties[16] = (Property){"e_shentsize", shentsize_str, 0};

    uint16_t e_shnum = (buf[49] << 8) | buf[48];
    char* shnum_str = malloc(4); sprintf(shnum_str, "%d", e_shnum);
    file->properties[17] = (Property){"e_shnum", shnum_str, 0};

    uint16_t e_shstrndx = (buf[51] << 8) | buf[50];
    char* shstrndx_str = malloc(4); sprintf(shstrndx_str, "%d", e_shstrndx);
    file->properties[18] = (Property){"e_shstrndx", shstrndx_str, 0};

    char* size_str = malloc(12); sprintf(size_str, "%zu", file->data_size);
    file->properties[19] = (Property){"FileSize", size_str, 0};
}

void print_properties(C32File* file) {
    if (!file->properties) {
        printf("No properties loaded. Call load_file() and parse_header() first.\n");
        return;
    }
    printf("C32 (.ELF32) Properties:\n");
    for (int i = 0; i < file->prop_count; i++) {
        Property p = file->properties[i];
        char* val = *(char**)p.value;
        if (p.is_hex) {
            printf("%s: %s\n", p.key, val);
        } else {
            printf("%s: %s\n", p.key, val);
        }
    }
}

int write_file(C32File* file, const char* output_filename) {
    if (!file->data) return -1;
    FILE* fp = fopen(output_filename, "wb");
    if (!fp) return -1;
    size_t written = fwrite(file->data, 1, file->data_size, fp);
    fclose(fp);
    if (written != file->data_size) return -1;
    printf("Written to %s\n", output_filename);
    return 0;
}

// Example usage in main:
int main(int argc, char* argv[]) {
    if (argc < 2) {
        printf("Usage: %s <c32file>\n", argv[0]);
        return 1;
    }
    C32File file = {0};
    if (load_file(argv[1], &file) != 0) {
        perror("Load failed");
        return 1;
    }
    parse_header(&file);
    print_properties(&file);
    write_file(&file, "output.c32");
    free_c32file(&file);
    return 0;
}

This C implementation manually handles little-endian unpacking and memory management for robustness, printing directly to stdout.