Task 198: .EXP File Format

Task 198: .EXP File Format

  1. The properties of the .EXP file format (Symbols Export File, using the COFF archive format) intrinsic to its file system are:

Archive Signature: The file starts with the 8-byte string "!\n".

Member Headers: Each member has a 60-byte header with fields for name (16 bytes), date (12 bytes), user ID (6 bytes), group ID (6 bytes), mode (8 bytes), size (10 bytes), and end of header (2 bytes "`\n").

First Linker Member: Contains the number of symbols (4 bytes), array of offsets to members (4 bytes each), and a string table of symbol names (null-terminated strings).

Second Linker Member: Contains the number of archive members (4 bytes), array of offsets to members (4 bytes each), number of symbols (4 bytes), array of symbol indices (2 bytes each), and a string table of symbol names (null-terminated strings).

Longnames Member: Optional, contains a series of null-terminated strings for member names longer than 15 characters.

Object Members: COFF object files containing the exported symbols and data.

  1. Two direct download links for .EXP files:

http://file.fyicenter.com/c/sample.exp

http://file.fyicenter.com/c/sample.exp (note: limited public samples available, this is the primary example found)

  1. Ghost blog embedded HTML JavaScript for drag and drop .EXP file to dump properties:
Drag and drop .EXP file here

Note: This is a basic parser that dumps the member headers. For full symbol dumping, expand the linker member parsing.

  1. Python class for .EXP file:
import struct

class ExpFile:
    def __init__(self, filename):
        self.filename = filename
    def read(self):
        with open(self.filename, 'rb') as f:
            data = f.read()
            self.parse(data)
    def parse(self, data):
        offset = 0
        signature = data[offset:offset+8].decode('ascii')
        offset += 8
        if signature != '!<arch>\n':
            print('Invalid signature')
            return
        print('Signature:', signature)
        while offset < len(data):
            name = data[offset:offset+16].decode('ascii').rstrip()
            offset += 16
            date = data[offset:offset+12].decode('ascii').rstrip()
            offset += 12
            user_id = data[offset:offset+6].decode('ascii').rstrip()
            offset += 6
            group_id = data[offset:offset+6].decode('ascii').rstrip()
            offset += 6
            mode = data[offset:offset+8].decode('ascii').rstrip()
            offset += 8
            size_str = data[offset:offset+10].decode('ascii').rstrip()
            size = int(size_str)
            offset += 10
            end_header = data[offset:offset+2].decode('ascii')
            offset += 2
            print(f'Member: {name}, Date: {date}, UserID: {user_id}, GroupID: {group_id}, Mode: {mode}, Size: {size}')
            if name == '/' or name == '/0': # First linker
                self.parse_linker_member(data[offset:offset+size], is_first=True)
            elif name == '//':
                self.parse_longnames(data[offset:offset+size])
            # Skip the member data
            offset += size
            if size % 2 != 0:
                offset += 1
    def parse_linker_member(self, data, is_first):
        offset = 0
        if is_first:
            num_symbols = struct.unpack('>I', data[offset:offset+4])[0]
            offset += 4
            offsets = [struct.unpack('>I', data[offset + i*4:offset + (i+1)*4])[0] for i in range(num_symbols)]
            offset += num_symbols * 4
            symbols = []
            for i in range(num_symbols):
                symbol = b''
                while data[offset] != 0:
                    symbol += data[offset:offset+1]
                    offset += 1
                offset += 1
                symbols.append(symbol.decode('ascii'))
            print('First Linker Symbols:', symbols)
        else:
            # Second linker parsing similar, with num_members, offsets, num_symbols, indices, string table
            # Add similar code
            pass
    def parse_longnames(self, data):
        print('Longnames:', data.decode('ascii').split('\0'))
    def write(self, out_filename):
        # To implement writing, reverse the parsing
        pass
    def print_properties(self):
        # Print parsed properties
        self.read()

# Usage
# exp = ExpFile('sample.exp')
# exp.print_properties()

Note: This is a basic implementation for reading and printing. Writing would require reverse engineering the parse.

  1. Java class for .EXP file:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class ExpFile {
    private String filename;
    public ExpFile(String filename) {
        this.filename = filename;
    }
    public void read() throws IOException {
        FileInputStream fis = new FileInputStream(filename);
        byte[] data = new byte[fis.available()];
        fis.read(data);
        fis.close();
        parse(data);
    }
    private void parse(byte[] data) {
        ByteBuffer buffer = ByteBuffer.wrap(data);
        buffer.order(ByteOrder.LITTLE_ENDIAN); // Adjust if needed
        String signature = new String(data, 0, 8);
        int offset = 8;
        if (!signature.equals("!<arch>\n")) {
            System.out.println("Invalid signature");
            return;
        }
        System.out.println("Signature: " + signature);
        while (offset < data.length) {
            String name = new String(data, offset, 16).trim();
            offset += 16;
            String date = new String(data, offset, 12).trim();
            offset += 12;
            String userId = new String(data, offset, 6).trim();
            offset += 6;
            String groupId = new String(data, offset, 6).trim();
            offset += 6;
            String mode = new String(data, offset, 8).trim();
            offset += 8;
            String sizeStr = new String(data, offset, 10).trim();
            int size = Integer.parseInt(sizeStr);
            offset += 10;
            String endHeader = new String(data, offset, 2);
            offset += 2;
            System.out.println("Member: " + name + ", Date: " + date + ", UserID: " + userId + ", GroupID: " + groupId + ", Mode: " + mode + ", Size: " + size);
            // Parse specific members
            int memberStart = offset;
            offset += size;
            if (size % 2 != 0) offset += 1;
        }
    }
    public void write(String outFilename) throws IOException {
        // Implement writing by reversing parse
    }
    public void printProperties() throws IOException {
        read();
    }
    public static void main(String[] args) throws IOException {
        ExpFile exp = new ExpFile("sample.exp");
        exp.printProperties();
    }
}

Note: Basic implementation, expand for full parsing.

  1. JavaScript class for .EXP file:
class ExpFile {
  constructor(filename) {
    this.filename = filename;
  }
  async read() {
    const response = await fetch(this.filename);
    const arrayBuffer = await response.arrayBuffer();
    const dataView = new DataView(arrayBuffer);
    this.parse(dataView);
  }
  parse(dataView) {
    let offset = 0;
    const signature = this.getString(dataView, offset, 8);
    offset += 8;
    if (signature !== '!<arch>\n') {
      console.log('Invalid signature');
      return;
    }
    console.log('Signature:', signature);
    while (offset < dataView.byteLength) {
      const name = this.getString(dataView, offset, 16).trim();
      offset += 16;
      const date = this.getString(dataView, offset, 12).trim();
      offset += 12;
      const userId = this.getString(dataView, offset, 6).trim();
      offset += 6;
      const groupId = this.getString(dataView, offset, 6).trim();
      offset += 6;
      const mode = this.getString(dataView, offset, 8).trim();
      offset += 8;
      const sizeStr = this.getString(dataView, offset, 10).trim();
      const size = parseInt(sizeStr, 10);
      offset += 10;
      const endHeader = this.getString(dataView, offset, 2);
      offset += 2;
      console.log(`Member: ${name}, Date: ${date}, UserID: ${userId}, GroupID: ${groupId}, Mode: ${mode}, Size: ${size}`);
      offset += size;
      if (size % 2 !== 0) offset += 1;
    }
  }
  getString(dataView, offset, length) {
    let str = '';
    for (let i = 0; i < length; i++) {
      str += String.fromCharCode(dataView.getUint8(offset + i));
    }
    return str;
  }
  write() {
    // Implement writing
  }
  printProperties() {
    this.read();
  }
}

// Usage
// const exp = new ExpFile('sample.exp');
 // exp.printProperties();

Note: Async for fetch, for local file use FileReader.

  1. C class for .EXP file:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
  char *filename;
} ExpFile;

ExpFile* exp_new(const char *filename) {
  ExpFile* exp = malloc(sizeof(ExpFile));
  exp->filename = strdup(filename);
  return exp;
}

void exp_read(ExpFile* exp) {
  FILE *f = fopen(exp->filename, "rb");
  if (!f) return;
  fseek(f, 0, SEEK_END);
  long length = ftell(f);
  fseek(f, 0, SEEK_SET);
  char *data = malloc(length);
  fread(data, 1, length, f);
  fclose(f);
  exp_parse(exp, data, length);
  free(data);
}

void exp_parse(ExpFile* exp, char *data, long length) {
  int offset = 0;
  char signature[9] = {0};
  strncpy(signature, data + offset, 8);
  offset += 8;
  if (strcmp(signature, "!<arch>\n") != 0) {
    printf("Invalid signature\n");
    return;
  }
  printf("Signature: %s\n", signature);
  while (offset < length) {
    char name[17] = {0};
    strncpy(name, data + offset, 16);
    offset += 16;
    char date[13] = {0};
    strncpy(date, data + offset, 12);
    offset += 12;
    char userId[7] = {0};
    strncpy(userId, data + offset, 6);
    offset += 6;
    char groupId[7] = {0};
    strncpy(groupId, data + offset, 6);
    offset += 6;
    char mode[9] = {0};
    strncpy(mode, data + offset, 8);
    offset += 8;
    char sizeStr[11] = {0};
    strncpy(sizeStr, data + offset, 10);
    int size = atoi(sizeStr);
    offset += 10;
    char endHeader[3] = {0};
    strncpy(endHeader, data + offset, 2);
    offset += 2;
    printf("Member: %s, Date: %s, UserID: %s, GroupID: %s, Mode: %s, Size: %d\n", name, date, userId, groupId, mode, size);
    offset += size;
    if (size % 2 != 0) offset += 1;
  }
}

void exp_write(ExpFile* exp, const char *out_filename) {
  // Implement writing
}

void exp_print_properties(ExpFile* exp) {
  exp_read(exp);
}

void exp_free(ExpFile* exp) {
  free(exp->filename);
  free(exp);
}

// Usage
// ExpFile* exp = exp_new("sample.exp");
// exp_print_properties(exp);
// exp_free(exp);

Note: Basic implementation, expand for full symbol parsing and writing.