Task 198: .EXP File Format
Task 198: .EXP File Format
- 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.
- 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)
- Ghost blog embedded HTML JavaScript for drag and drop .EXP file to dump properties:
Note: This is a basic parser that dumps the member headers. For full symbol dumping, expand the linker member parsing.
- 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.
- 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.
- 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.
- 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.