Task 705: .SWG File Format

Task 705: .SWG File Format

.SWG File Format Specifications

The .SWG file format is a specialized archive format used by the SourceWare Archival Group (SWAG) for storing collections of Pascal source code snippets. It comes in "old" and "new" variants, with the new variant being a derivative of the LHA (LZH) archive format (header level 0) with modifications, including a custom compression method and an additional 165 bytes of header data preceding the filename field. The format is obscure and largely historical (dating back to the 1990s), with partial documentation. The structure is based on a stream of records, each representing a member file in the archive, ended by a header length of 0. The custom compression for "-sw1-" is undocumented, so decoding the body content is not fully possible without additional tools (e.g., XX34 utility for some files). The old format is even less documented, often starting with the string "SWAGOLX.EXE".

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

  • Header Checksum: A 1-byte unsigned integer representing the checksum of the header (excluding the header length byte).
  • Compression Method: A 5-byte ASCII string identifying the compression type (e.g., "-sw0-" for uncompressed or "-sw1-" for custom compressed in new format; at offset 2 in the record).
  • Compressed Size: A 4-byte unsigned integer (little-endian) indicating the size of the compressed member file data.
  • Uncompressed Size: A 4-byte unsigned integer (little-endian) indicating the size of the uncompressed member file data.
  • File Timestamp: A 4-byte DOS datetime structure (little-endian) representing the original file's date and time.
  • Attribute: A 1-byte unsigned integer representing the file or directory attribute (e.g., DOS file attributes).
  • LHA Level: A 1-byte unsigned integer indicating the header level (0 for the new format base).
  • Extra Header: 165 bytes of additional header data specific to .SWG (new format; contents undocumented, inserted before the filename length field).
  • Filename: A variable-length ASCII string (preceded by a 1-byte length field) representing the name of the member file.
  • File Uncompressed CRC16: A 2-byte unsigned integer (little-endian) checksum (CRC-16) of the uncompressed member file data.

Two direct download links for files of format .SWG:

Ghost blog embedded HTML JavaScript for drag-and-drop .SWG file dump:

Drag and drop a .SWG file here
  1. Python class for .SWG:
import struct
import datetime

class SWGFile:
    def __init__(self, filename):
        self.filename = filename
        self.members = []

    def read(self):
        with open(self.filename, 'rb') as f:
            data = f.read()
        offset = 0
        while offset < len(data):
            header_len = data[offset]
            if header_len == 0:
                break
            offset += 1
            checksum = data[offset]
            method = data[offset + 1:offset + 6].decode('ascii')
            compressed_size = struct.unpack('<I', data[offset + 6:offset + 10])[0]
            uncompressed_size = struct.unpack('<I', data[offset + 10:offset + 14])[0]
            timestamp_bytes = data[offset + 14:offset + 18]
            time_val, date_val = struct.unpack('<HH', timestamp_bytes)
            year = 1980 + (date_val >> 9)
            month = (date_val >> 5) & 0xF
            day = date_val & 0x1F
            hour = time_val >> 11
            minute = (time_val >> 5) & 0x3F
            second = (time_val & 0x1F) * 2
            timestamp = datetime.datetime(year, month, day, hour, minute, second)
            attr = data[offset + 18]
            level = data[offset + 19]
            extra_header = data[offset + 20:offset + 20 + 165]
            filename_len = data[offset + 20 + 165]
            filename = data[offset + 21 + 165:offset + 21 + 165 + filename_len].decode('ascii')
            crc16 = struct.unpack('<H', data[offset + 21 + 165 + filename_len:offset + 23 + 165 + filename_len])[0]
            body = data[offset + header_len + 1:offset + header_len + 1 + compressed_size]  # For decode, but compression unknown
            self.members.append({
                'checksum': checksum,
                'method': method,
                'compressed_size': compressed_size,
                'uncompressed_size': uncompressed_size,
                'timestamp': timestamp,
                'attr': attr,
                'level': level,
                'extra_header': extra_header,
                'filename': filename,
                'crc16': crc16,
                'body': body  # Compressed data
            })
            offset += header_len + compressed_size

    def print_properties(self):
        for member in self.members:
            print(f"Member File: {member['filename']}")
            print(f"Header Checksum: {member['checksum']}")
            print(f"Compression Method: {member['method']}")
            print(f"Compressed Size: {member['compressed_size']}")
            print(f"Uncompressed Size: {member['uncompressed_size']}")
            print(f"File Timestamp: {member['timestamp']}")
            print(f"Attribute: {member['attr']}")
            print(f"LHA Level: {member['level']}")
            print(f"Extra Header: {member['extra_header']}")
            print(f"Filename: {member['filename']}")
            print(f"File Uncompressed CRC16: {member['crc16']}")
            print("---")

    def write(self, new_filename):
        with open(new_filename, 'wb') as f:
            for member in self.members:
                extra_header = member['extra_header']
                filename_bytes = member['filename'].encode('ascii')
                filename_len = len(filename_bytes)
                header_len = 20 + 165 + 1 + filename_len + 2  # Base + extra + fn_len + fn + crc16
                f.write(struct.pack('<B', header_len))
                checksum = member['checksum']  # Assume same, or recalculate if needed
                f.write(struct.pack('<B', checksum))
                f.write(member['method'].encode('ascii'))
                f.write(struct.pack('<I', member['compressed_size']))
                f.write(struct.pack('<I', member['uncompressed_size']))
                timestamp = member['timestamp']
                date_val = ((timestamp.year - 1980) << 9) | (timestamp.month << 5) | timestamp.day
                time_val = (timestamp.hour << 11) | (timestamp.minute << 5) | (timestamp.second // 2)
                f.write(struct.pack('<HH', time_val, date_val))
                f.write(struct.pack('<B', member['attr']))
                f.write(struct.pack('<B', member['level']))
                f.write(extra_header)
                f.write(struct.pack('<B', filename_len))
                f.write(filename_bytes)
                f.write(struct.pack('<H', member['crc16']))
                f.write(member['body'])
            f.write(b'\x00')  # End marker

# Example usage:
# swg = SWGFile('example.swg')
# swg.read()
# swg.print_properties()
# swg.write('new_example.swg')
  1. Java class for .SWG:
import java.io.*;
import java.nio.*;
import java.nio.file.*;
import java.util.*;
import java.util.List;

public class SWGFile {
    private String filename;
    private List<Map<String, Object>> members = new ArrayList<>();

    public SWGFile(String filename) {
        this.filename = filename;
    }

    public void read() throws IOException {
        byte[] data = Files.readAllBytes(Paths.get(filename));
        ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
        int offset = 0;
        while (offset < data.length) {
            byte headerLen = buffer.get(offset);
            if (headerLen == 0) break;
            offset++;
            byte checksum = buffer.get(offset);
            String method = new String(data, offset + 1, 5, "ASCII");
            int compressedSize = buffer.getInt(offset + 6);
            int uncompressedSize = buffer.getInt(offset + 10);
            int timeVal = buffer.getShort(offset + 14) & 0xFFFF;
            int dateVal = buffer.getShort(offset + 16) & 0xFFFF;
            Calendar cal = Calendar.getInstance();
            cal.set(1980 + (dateVal >> 9), ((dateVal >> 5) & 0xF) - 1, dateVal & 0x1F,
                    timeVal >> 11, (timeVal >> 5) & 0x3F, (timeVal & 0x1F) * 2);
            Date timestamp = cal.getTime();
            byte attr = buffer.get(offset + 18);
            byte level = buffer.get(offset + 19);
            byte[] extraHeader = new byte[165];
            System.arraycopy(data, offset + 20, extraHeader, 0, 165);
            byte filenameLen = buffer.get(offset + 20 + 165);
            String filenameStr = new String(data, offset + 21 + 165, filenameLen, "ASCII");
            short crc16 = buffer.getShort(offset + 21 + 165 + filenameLen);
            byte[] body = new byte[compressedSize];
            System.arraycopy(data, offset + headerLen, body, 0, compressedSize);
            Map<String, Object> member = new HashMap<>();
            member.put("checksum", checksum);
            member.put("method", method);
            member.put("compressed_size", compressedSize);
            member.put("uncompressed_size", uncompressedSize);
            member.put("timestamp", timestamp);
            member.put("attr", attr);
            member.put("level", level);
            member.put("extra_header", extraHeader);
            member.put("filename", filenameStr);
            member.put("crc16", crc16);
            member.put("body", body);
            members.add(member);
            offset += headerLen + compressedSize;
        }
    }

    public void printProperties() {
        for (Map<String, Object> member : members) {
            System.out.println("Member File: " + member.get("filename"));
            System.out.println("Header Checksum: " + member.get("checksum"));
            System.out.println("Compression Method: " + member.get("method"));
            System.out.println("Compressed Size: " + member.get("compressed_size"));
            System.out.println("Uncompressed Size: " + member.get("uncompressed_size"));
            System.out.println("File Timestamp: " + member.get("timestamp"));
            System.out.println("Attribute: " + member.get("attr"));
            System.out.println("LHA Level: " + member.get("level"));
            byte[] extra = (byte[]) member.get("extra_header");
            System.out.print("Extra Header: ");
            for (byte b : extra) System.out.print(String.format("%02X ", b));
            System.out.println();
            System.out.println("Filename: " + member.get("filename"));
            System.out.println("File Uncompressed CRC16: " + member.get("crc16"));
            System.out.println("---");
        }
    }

    public void write(String newFilename) throws IOException {
        try (FileOutputStream fos = new FileOutputStream(newFilename)) {
            for (Map<String, Object> member : members) {
                byte[] extraHeader = (byte[]) member.get("extra_header");
                String fn = (String) member.get("filename");
                byte[] fnBytes = fn.getBytes("ASCII");
                int fnLen = fnBytes.length;
                int headerLen = 20 + 165 + 1 + fnLen + 2;
                fos.write(headerLen);
                byte checksum = (byte) member.get("checksum");
                fos.write(checksum);
                fos.write(((String) member.get("method")).getBytes("ASCII"));
                ByteBuffer bb = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
                fos.write(bb.putInt((int) member.get("compressed_size")).array());
                fos.write(bb.putInt(0).putInt((int) member.get("uncompressed_size")).array());
                Date ts = (Date) member.get("timestamp");
                Calendar cal = Calendar.getInstance();
                cal.setTime(ts);
                int dateVal = ((cal.get(Calendar.YEAR) - 1980) << 9) | ((cal.get(Calendar.MONTH) + 1) << 5) | cal.get(Calendar.DAY_OF_MONTH);
                int timeVal = (cal.get(Calendar.HOUR_OF_DAY) << 11) | (cal.get(Calendar.MINUTE) << 5) | (cal.get(Calendar.SECOND) / 2);
                fos.write(bb.putShort(0).putShort((short) timeVal).array());
                fos.write(bb.putShort(0).putShort((short) dateVal).array());
                fos.write((byte) member.get("attr"));
                fos.write((byte) member.get("level"));
                fos.write(extraHeader);
                fos.write(fnLen);
                fos.write(fnBytes);
                fos.write(bb.putShort(0).putShort((short) member.get("crc16")).array());
                fos.write((byte[]) member.get("body"));
            }
            fos.write(0);
        }
    }

    // Example usage:
    // SWGFile swg = new SWGFile("example.swg");
    // swg.read();
    // swg.printProperties();
    // swg.write("new_example.swg");
}
  1. JavaScript class for .SWG:
class SWGFile {
    constructor(filename) {
        this.filename = filename;
        this.members = [];
    }

    async read() {
        const response = await fetch(this.filename); // Assuming local or CORS allowed
        const arrayBuffer = await response.arrayBuffer();
        const data = new Uint8Array(arrayBuffer);
        let offset = 0;
        while (offset < data.length) {
            const headerLen = data[offset];
            if (headerLen === 0) break;
            offset++;
            const checksum = data[offset];
            const method = String.fromCharCode(...data.slice(offset + 1, offset + 6));
            const compressedSize = new DataView(arrayBuffer).getUint32(offset + 6, true);
            const uncompressedSize = new DataView(arrayBuffer).getUint32(offset + 10, true);
            const timeVal = new DataView(arrayBuffer).getUint16(offset + 14, true);
            const dateVal = new DataView(arrayBuffer).getUint16(offset + 16, true);
            const year = 1980 + (dateVal >> 9);
            const month = (dateVal >> 5) & 0xF;
            const day = dateVal & 0x1F;
            const hour = timeVal >> 11;
            const minute = (timeVal >> 5) & 0x3F;
            const second = (timeVal & 0x1F) * 2;
            const timestamp = new Date(year, month - 1, day, hour, minute, second);
            const attr = data[offset + 18];
            const level = data[offset + 19];
            const extraHeader = data.slice(offset + 20, offset + 20 + 165);
            const filenameLen = data[offset + 20 + 165];
            const filename = String.fromCharCode(...data.slice(offset + 21 + 165, offset + 21 + 165 + filenameLen));
            const crc16 = new DataView(arrayBuffer).getUint16(offset + 21 + 165 + filenameLen, true);
            const body = data.slice(offset + headerLen, offset + headerLen + compressedSize);
            this.members.push({
                checksum,
                method,
                compressedSize,
                uncompressedSize,
                timestamp,
                attr,
                level,
                extraHeader,
                filename,
                crc16,
                body
            });
            offset += headerLen + compressedSize;
        }
    }

    printProperties() {
        this.members.forEach(member => {
            console.log(`Member File: ${member.filename}`);
            console.log(`Header Checksum: ${member.checksum}`);
            console.log(`Compression Method: ${member.method}`);
            console.log(`Compressed Size: ${member.compressedSize}`);
            console.log(`Uncompressed Size: ${member.uncompressedSize}`);
            console.log(`File Timestamp: ${member.timestamp}`);
            console.log(`Attribute: ${member.attr}`);
            console.log(`LHA Level: ${member.level}`);
            console.log(`Extra Header: ${Array.from(member.extraHeader).map(b => b.toString(16).padStart(2, '0')).join(' ')}`);
            console.log(`Filename: ${member.filename}`);
            console.log(`File Uncompressed CRC16: ${member.crc16}`);
            console.log('---');
        });
    }

    write(newFilename) {
        let buffer = new ArrayBuffer(0);
        let view = new DataView(buffer);
        // Note: Writing in JS to file requires Node.js or browser download; this simulates buffer construction
        this.members.forEach(member => {
            const extraHeader = member.extraHeader;
            const fnBytes = new TextEncoder().encode(member.filename);
            const fnLen = fnBytes.length;
            const headerLen = 20 + 165 + 1 + fnLen + 2;
            const tempBuffer = new ArrayBuffer(headerLen + 1 + member.compressedSize);
            const tempView = new DataView(tempBuffer);
            tempView.setUint8(0, headerLen);
            tempView.setUint8(1, member.checksum);
            const methodBytes = new TextEncoder().encode(member.method);
            for (let i = 0; i < 5; i++) tempView.setUint8(2 + i, methodBytes[i]);
            tempView.setUint32(7, member.compressedSize, true);
            tempView.setUint32(11, member.uncompressedSize, true);
            const ts = member.timestamp;
            const dateVal = ((ts.getFullYear() - 1980) << 9) | ((ts.getMonth() + 1) << 5) | ts.getDate();
            const timeVal = (ts.getHours() << 11) | (ts.getMinutes() << 5) | (ts.getSeconds() / 2);
            tempView.setUint16(15, timeVal, true);
            tempView.setUint16(17, dateVal, true);
            tempView.setUint8(19, member.attr);
            tempView.setUint8(20, member.level);
            for (let i = 0; i < 165; i++) tempView.setUint8(21 + i, extraHeader[i]);
            tempView.setUint8(21 + 165, fnLen);
            for (let i = 0; i < fnLen; i++) tempView.setUint8(22 + 165 + i, fnBytes[i]);
            tempView.setUint16(22 + 165 + fnLen, member.crc16, true);
            for (let i = 0; i < member.compressedSize; i++) tempView.setUint8(1 + headerLen + i, member.body[i]);
            // Append to main buffer (simulated; in practice, use Blob or fs.write)
            buffer = new ArrayBuffer(buffer.byteLength + tempBuffer.byteLength);
            new Uint8Array(buffer).set(new Uint8Array(view.buffer));
            new Uint8Array(buffer).set(new Uint8Array(tempBuffer), view.buffer.byteLength);
            view = new DataView(buffer);
        });
        new Uint8Array(buffer).set([0], buffer.byteLength - 1); // End marker
        // To save: in Node.js, fs.writeFileSync(newFilename, new Uint8Array(buffer))
        console.log('Written to buffer (save manually in environment).');
    }
}

// Example usage:
// const swg = new SWGFile('example.swg');
// await swg.read();
// swg.printProperties();
// swg.write('new_example.swg');
  1. C class for .SWG:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <time.h>

typedef struct {
    uint8_t checksum;
    char method[6];
    uint32_t compressed_size;
    uint32_t uncompressed_size;
    struct tm timestamp;
    uint8_t attr;
    uint8_t level;
    uint8_t* extra_header;
    char* filename;
    uint16_t crc16;
    uint8_t* body;
} SWGMember;

typedef struct {
    char* filename;
    SWGMember* members;
    size_t member_count;
} SWGFile;

SWGFile* swg_create(const char* filename) {
    SWGFile* swg = malloc(sizeof(SWGFile));
    swg->filename = strdup(filename);
    swg->members = NULL;
    swg->member_count = 0;
    return swg;
}

void swg_read(SWGFile* swg) {
    FILE* f = fopen(swg->filename, "rb");
    fseek(f, 0, SEEK_END);
    size_t size = ftell(f);
    fseek(f, 0, SEEK_SET);
    uint8_t* data = malloc(size);
    fread(data, 1, size, f);
    fclose(f);

    size_t offset = 0;
    while (offset < size) {
        uint8_t header_len = data[offset];
        if (header_len == 0) break;
        offset++;
        SWGMember member;
        member.checksum = data[offset];
        strncpy(member.method, (char*)(data + offset + 1), 5);
        member.method[5] = '\0';
        member.compressed_size = *(uint32_t*)(data + offset + 6);
        member.uncompressed_size = *(uint32_t*)(data + offset + 10);
        uint16_t time_val = *(uint16_t*)(data + offset + 14);
        uint16_t date_val = *(uint16_t*)(data + offset + 16);
        member.timestamp.tm_year = 80 + (date_val >> 9);
        member.timestamp.tm_mon = ((date_val >> 5) & 0xF) - 1;
        member.timestamp.tm_mday = date_val & 0x1F;
        member.timestamp.tm_hour = time_val >> 11;
        member.timestamp.tm_min = (time_val >> 5) & 0x3F;
        member.timestamp.tm_sec = (time_val & 0x1F) * 2;
        member.timestamp.tm_isdst = -1;
        member.attr = data[offset + 18];
        member.level = data[offset + 19];
        member.extra_header = malloc(165);
        memcpy(member.extra_header, data + offset + 20, 165);
        uint8_t filename_len = data[offset + 20 + 165];
        member.filename = malloc(filename_len + 1);
        memcpy(member.filename, data + offset + 21 + 165, filename_len);
        member.filename[filename_len] = '\0';
        member.crc16 = *(uint16_t*)(data + offset + 21 + 165 + filename_len);
        member.body = malloc(member.compressed_size);
        memcpy(member.body, data + offset + header_len, member.compressed_size);
        swg->members = realloc(swg->members, sizeof(SWGMember) * (swg->member_count + 1));
        swg->members[swg->member_count++] = member;
        offset += header_len + member.compressed_size;
    }
    free(data);
}

void swg_print_properties(const SWGFile* swg) {
    for (size_t i = 0; i < swg->member_count; i++) {
        SWGMember m = swg->members[i];
        printf("Member File: %s\n", m.filename);
        printf("Header Checksum: %u\n", m.checksum);
        printf("Compression Method: %s\n", m.method);
        printf("Compressed Size: %u\n", m.compressed_size);
        printf("Uncompressed Size: %u\n", m.uncompressed_size);
        char time_str[20];
        strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", &m.timestamp);
        printf("File Timestamp: %s\n", time_str);
        printf("Attribute: %u\n", m.attr);
        printf("LHA Level: %u\n", m.level);
        printf("Extra Header: ");
        for (int j = 0; j < 165; j++) printf("%02X ", m.extra_header[j]);
        printf("\n");
        printf("Filename: %s\n", m.filename);
        printf("File Uncompressed CRC16: %u\n", m.crc16);
        printf("---\n");
    }
}

void swg_write(const SWGFile* swg, const char* new_filename) {
    FILE* f = fopen(new_filename, "wb");
    for (size_t i = 0; i < swg->member_count; i++) {
        SWGMember m = swg->members[i];
        size_t fn_len = strlen(m.filename);
        uint8_t header_len = 20 + 165 + 1 + fn_len + 2;
        fwrite(&header_len, 1, 1, f);
        fwrite(&m.checksum, 1, 1, f);
        fwrite(m.method, 1, 5, f);
        fwrite(&m.compressed_size, 4, 1, f);
        fwrite(&m.uncompressed_size, 4, 1, f);
        uint16_t date_val = ((m.timestamp.tm_year - 80) << 9) | ((m.timestamp.tm_mon + 1) << 5) | m.timestamp.tm_mday;
        uint16_t time_val = (m.timestamp.tm_hour << 11) | (m.timestamp.tm_min << 5) | (m.timestamp.tm_sec / 2);
        fwrite(&time_val, 2, 1, f);
        fwrite(&date_val, 2, 1, f);
        fwrite(&m.attr, 1, 1, f);
        fwrite(&m.level, 1, 1, f);
        fwrite(m.extra_header, 1, 165, f);
        uint8_t fn_len_byte = (uint8_t)fn_len;
        fwrite(&fn_len_byte, 1, 1, f);
        fwrite(m.filename, 1, fn_len, f);
        fwrite(&m.crc16, 2, 1, f);
        fwrite(m.body, 1, m.compressed_size, f);
    }
    uint8_t end = 0;
    fwrite(&end, 1, 1, f);
    fclose(f);
}

void swg_destroy(SWGFile* swg) {
    for (size_t i = 0; i < swg->member_count; i++) {
        free(swg->members[i].extra_header);
        free(swg->members[i].filename);
        free(swg->members[i].body);
    }
    free(swg->members);
    free(swg->filename);
    free(swg);
}

// Example usage:
// SWGFile* swg = swg_create("example.swg");
// swg_read(swg);
// swg_print_properties(swg);
// swg_write(swg, "new_example.swg");
// swg_destroy(swg);