Task 803: .WAD File Format

Task 803: .WAD File Format

1. List of Properties Intrinsic to the .WAD File Format

Based on the specifications of the .WAD file format, the following properties are intrinsic to its structure and organization, akin to a simple file system for resource storage:

  1. File Signature: A 4-byte ASCII string indicating the type ("IWAD" for internal WAD containing core game data or "PWAD" for patch WAD with additional or modified data).
  2. Header Structure: A fixed 12-byte header comprising the signature (bytes 0-3), number of lumps (bytes 4-7, signed 32-bit little-endian integer), and directory offset (bytes 8-11, signed 32-bit little-endian integer).
  3. Directory Format: An array of entries starting at the directory offset, with each entry being 16 bytes: lump offset (4 bytes, signed 32-bit little-endian integer), lump size (4 bytes, signed 32-bit little-endian integer), and lump name (8 bytes, ASCII string, null-terminated if shorter than 8 characters and null-padded for compatibility).
  4. Lump Addressing: Data lumps are stored at absolute file offsets specified in the directory, allowing for potential overlaps or shared offsets among lumps.
  5. Endianness: Little-endian byte order for all multi-byte integers by default, with exceptions in certain console ports (e.g., big-endian in Atari Jaguar variants).
  6. Integer Type: All multi-byte numerical values are 4-byte signed integers.
  7. Lump Metadata: Each lump includes a name (up to 8 characters, case-sensitive, no extensions), offset, and size (which may be 0 for marker lumps).
  8. Marker Lumps: Zero-length lumps used as delimiters for resource sections (e.g., "F_START"/"F_END" for flats, "S_START"/"S_END" for sprites).
  9. Compression Support: Optional LZSS compression in specific variants (e.g., console ports), triggered by the high bit (0x80) in the first byte of the lump name; compressed data lacks a stored size and requires stream-based decompression.
  10. Map Lump Ordering: For map data, lumps must follow a fixed sequence after the map name lump (e.g., "THINGS", "LINEDEFS", "SIDEDEFS").
  11. Extensibility: The format supports arbitrary lump types and contents, enabling use beyond original games.
  12. No Built-in Security: Absence of encryption, hashing, or integrity checks; data is stored in raw form.
  13. File Extension Convention: Typically ".wad", though not enforced by the format.

These properties define the core architecture of the .WAD format, ensuring compatibility across engines while allowing flexibility.

The following are direct download links to sample .WAD files from publicly available sources:

Note: These files are provided for educational and development purposes; ensure compliance with applicable copyrights and licenses when using them.

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

The following is a self-contained HTML snippet with embedded JavaScript that can be embedded in a Ghost blog post (or any HTML-compatible platform). It creates a drop zone where users can drag and drop a .WAD file, parses it, and displays the intrinsic properties (header details and lump list) on the screen.

Drag and drop a .WAD file here

This code handles basic parsing without compression support and assumes standard little-endian format.

4. Python Class for .WAD File Handling

The following Python class can open, decode, read, write, and print the intrinsic properties of a .WAD file to the console. It uses the struct module for binary parsing and supports writing a new .WAD file based on parsed data.

import struct

class WADFile:
    def __init__(self, filename):
        with open(filename, 'rb') as f:
            self.data = f.read()
        
        # Decode header
        self.identification = struct.unpack('<4s', self.data[0:4])[0].decode('ascii')
        self.numlumps = struct.unpack('<i', self.data[4:8])[0]
        self.infotableofs = struct.unpack('<i', self.data[8:12])[0]
        
        # Decode directory and extract lump data
        self.lumps = []
        for i in range(self.numlumps):
            pos = self.infotableofs + i * 16
            lump_offset = struct.unpack('<i', self.data[pos:pos+4])[0]
            lump_size = struct.unpack('<i', self.data[pos+4:pos+8])[0]
            lump_name = struct.unpack('<8s', self.data[pos+8:pos+16])[0].decode('ascii').rstrip('\x00')
            lump_data = self.data[lump_offset:lump_offset + lump_size]
            self.lumps.append({'name': lump_name, 'offset': lump_offset, 'size': lump_size, 'data': lump_data})

    def print_properties(self):
        print(f"Identification: {self.identification}")
        print(f"Number of lumps: {self.numlumps}")
        print(f"Directory offset: {self.infotableofs}")
        print("Lumps:")
        for lump in self.lumps:
            print(f"Name: {lump['name']}, Offset: {lump['offset']}, Size: {lump['size']}")

    def write(self, filename):
        # Build header
        header = struct.pack('<4sii', self.identification.encode('ascii'), self.numlumps, 12)  # Temporary dir offset
        
        # Build data section and track offsets
        data_section = b''
        directory = b''
        current_offset = 12 + (self.numlumps * 16)  # Header + directory size
        
        for lump in self.lumps:
            lump['offset'] = current_offset
            data_section += lump['data']
            directory += struct.pack('<ii8s', lump['offset'], lump['size'], lump['name'].encode('ascii').ljust(8, b'\x00'))
            current_offset += lump['size']
        
        # Update directory offset in header
        header = struct.pack('<4sii', self.identification.encode('ascii'), self.numlumps, 12)
        
        # Write to file
        with open(filename, 'wb') as f:
            f.write(header + directory + data_section)

Usage example: wad = WADFile('example.wad'); wad.print_properties(); wad.write('output.wad'). This implementation assumes no compression.

5. Java Class for .WAD File Handling

The following Java class can open, decode, read, write, and print the intrinsic properties of a .WAD file to the console. It uses java.nio for binary handling.

import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
import java.nio.file.*;

public class WadFile {
    private String identification;
    private int numLumps;
    private int infoTableOfs;
    private Lump[] lumps;

    static class Lump {
        String name;
        int offset;
        int size;
        byte[] data;
    }

    public WadFile(String filename) throws IOException {
        byte[] data = Files.readAllBytes(Paths.get(filename));
        ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);

        // Decode header
        byte[] idBytes = new byte[4];
        buffer.get(idBytes, 0, 4);
        identification = new String(idBytes);
        numLumps = buffer.getInt();
        infoTableOfs = buffer.getInt();

        // Decode directory
        lumps = new Lump[numLumps];
        for (int i = 0; i < numLumps; i++) {
            int pos = infoTableOfs + i * 16;
            buffer.position(pos);
            Lump lump = new Lump();
            lump.offset = buffer.getInt();
            lump.size = buffer.getInt();
            byte[] nameBytes = new byte[8];
            buffer.get(nameBytes);
            lump.name = new String(nameBytes).trim();
            lump.data = new byte[lump.size];
            System.arraycopy(data, lump.offset, lump.data, 0, lump.size);
            lumps[i] = lump;
        }
    }

    public void printProperties() {
        System.out.println("Identification: " + identification);
        System.out.println("Number of lumps: " + numLumps);
        System.out.println("Directory offset: " + infoTableOfs);
        System.out.println("Lumps:");
        for (Lump lump : lumps) {
            System.out.println("Name: " + lump.name + ", Offset: " + lump.offset + ", Size: " + lump.size);
        }
    }

    public void write(String filename) throws IOException {
        try (RandomAccessFile raf = new RandomAccessFile(filename, "rw");
             FileChannel channel = raf.getChannel()) {
            // Prepare header (temporary dir offset)
            ByteBuffer header = ByteBuffer.allocate(12).order(ByteOrder.LITTLE_ENDIAN);
            header.put(identification.getBytes());
            header.putInt(numLumps);
            header.putInt(12);  // Temporary
            header.flip();
            channel.write(header);

            // Build directory and data
            ByteBuffer directory = ByteBuffer.allocate(numLumps * 16).order(ByteOrder.LITTLE_ENDIAN);
            ByteBuffer dataSection = ByteBuffer.allocate(lumps.length > 0 ? lumps[0].data.length * lumps.length : 0).order(ByteOrder.LITTLE_ENDIAN); // Approximate
            int currentOffset = 12 + (numLumps * 16);

            for (Lump lump : lumps) {
                lump.offset = currentOffset;
                directory.putInt(lump.offset);
                directory.putInt(lump.size);
                byte[] nameBytes = lump.name.getBytes();
                byte[] paddedName = new byte[8];
                System.arraycopy(nameBytes, 0, paddedName, 0, Math.min(nameBytes.length, 8));
                directory.put(paddedName);
                dataSection.put(lump.data);
                currentOffset += lump.size;
            }
            directory.flip();
            dataSection.flip();

            // Write directory and data
            channel.write(directory);
            channel.write(dataSection);
        }
    }
}

Usage example: WadFile wad = new WadFile("example.wad"); wad.printProperties(); wad.write("output.wad");. Compression is not supported.

6. JavaScript Class for .WAD File Handling

The following JavaScript class can open (via ArrayBuffer), decode, read, write (to Blob for download), and print the intrinsic properties of a .WAD file to the console. It is suitable for browser or Node.js environments with buffer support.

class WADFile {
  constructor(buffer) {
    const view = new DataView(buffer);
    const uint8 = new Uint8Array(buffer);

    // Decode header
    this.identification = String.fromCharCode(...uint8.slice(0, 4));
    this.numlumps = view.getInt32(4, true);
    this.infotableofs = view.getInt32(8, true);

    // Decode directory
    this.lumps = [];
    for (let i = 0; i < this.numlumps; i++) {
      const pos = this.infotableofs + i * 16;
      const lump = {
        offset: view.getInt32(pos, true),
        size: view.getInt32(pos + 4, true),
        name: String.fromCharCode(...uint8.slice(pos + 8, pos + 16)).replace(/\0/g, '').trim(),
        data: buffer.slice(view.getInt32(pos, true), view.getInt32(pos, true) + view.getInt32(pos + 4, true))
      };
      this.lumps.push(lump);
    }
  }

  printProperties() {
    console.log(`Identification: ${this.identification}`);
    console.log(`Number of lumps: ${this.numlumps}`);
    console.log(`Directory offset: ${this.infotableofs}`);
    console.log('Lumps:');
    this.lumps.forEach(lump => {
      console.log(`Name: ${lump.name}, Offset: ${lump.offset}, Size: ${lump.size}`);
    });
  }

  write() {
    // Build header
    const header = new ArrayBuffer(12);
    const headerView = new DataView(header);
    const uint8Header = new Uint8Array(header);
    uint8Header.set(this.identification.split('').map(c => c.charCodeAt(0)), 0);
    headerView.setInt32(4, this.numlumps, true);
    headerView.setInt32(8, 12, true);  // Temporary dir offset

    // Build directory and data
    const directory = new ArrayBuffer(this.numlumps * 16);
    const dirView = new DataView(directory);
    const dirUint8 = new Uint8Array(directory);
    let dataSection = new ArrayBuffer(0);
    let currentOffset = 12 + (this.numlumps * 16);

    this.lumps.forEach((lump, i) => {
      lump.offset = currentOffset;
      dirView.setInt32(i * 16, lump.offset, true);
      dirView.setInt32(i * 16 + 4, lump.size, true);
      const nameBytes = lump.name.split('').map(c => c.charCodeAt(0));
      dirUint8.set(nameBytes.slice(0, 8), i * 16 + 8);
      dataSection = this.concatArrayBuffers(dataSection, lump.data);
      currentOffset += lump.size;
    });

    // Combine and return Blob for download
    const fullBuffer = this.concatArrayBuffers(header, directory, dataSection);
    return new Blob([fullBuffer], { type: 'application/octet-stream' });
  }

  concatArrayBuffers(...buffers) {
    const totalLength = buffers.reduce((acc, buf) => acc + buf.byteLength, 0);
    const result = new Uint8Array(totalLength);
    let offset = 0;
    buffers.forEach(buf => {
      result.set(new Uint8Array(buf), offset);
      offset += buf.byteLength;
    });
    return result.buffer;
  }
}

Usage example (browser): const reader = new FileReader(); reader.onload = () => { const wad = new WADFile(reader.result); wad.printProperties(); const blob = wad.write(); /* Download blob */ }; reader.readAsArrayBuffer(file);. No compression support.

7. C Implementation for .WAD File Handling (Using Structs and Functions)

C does not have classes in the traditional sense, but the following implementation uses structs and functions to achieve equivalent functionality. It can open, decode, read, write, and print the intrinsic properties of a .WAD file to the console. Compile with a standard C compiler (e.g., gcc).

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

typedef struct {
    char identification[4];
    int32_t numlumps;
    int32_t infotableofs;
} WADHeader;

typedef struct {
    char name[9];  // Null-terminated
    int32_t offset;
    int32_t size;
    uint8_t *data;
} WADLump;

typedef struct {
    WADHeader header;
    WADLump *lumps;
} WADFile;

WADFile* wad_open(const char *filename) {
    FILE *f = fopen(filename, "rb");
    if (!f) return NULL;

    fseek(f, 0, SEEK_END);
    long filesize = ftell(f);
    fseek(f, 0, SEEK_SET);

    uint8_t *data = malloc(filesize);
    fread(data, 1, filesize, f);
    fclose(f);

    WADFile *wad = malloc(sizeof(WADFile));
    memcpy(wad->header.identification, data, 4);
    memcpy(&wad->header.numlumps, data + 4, 4);
    memcpy(&wad->header.infotableofs, data + 8, 4);

    wad->lumps = malloc(wad->header.numlumps * sizeof(WADLump));
    for (int i = 0; i < wad->header.numlumps; i++) {
        int pos = wad->header.infotableofs + i * 16;
        memcpy(&wad->lumps[i].offset, data + pos, 4);
        memcpy(&wad->lumps[i].size, data + pos + 4, 4);
        memcpy(wad->lumps[i].name, data + pos + 8, 8);
        wad->lumps[i].name[8] = '\0';
        wad->lumps[i].data = malloc(wad->lumps[i].size);
        memcpy(wad->lumps[i].data, data + wad->lumps[i].offset, wad->lumps[i].size);
    }

    free(data);
    return wad;
}

void wad_print_properties(WADFile *wad) {
    printf("Identification: %.4s\n", wad->header.identification);
    printf("Number of lumps: %d\n", wad->header.numlumps);
    printf("Directory offset: %d\n", wad->header.infotableofs);
    printf("Lumps:\n");
    for (int i = 0; i < wad->header.numlumps; i++) {
        printf("Name: %s, Offset: %d, Size: %d\n", wad->lumps[i].name, wad->lumps[i].offset, wad->lumps[i].size);
    }
}

void wad_write(WADFile *wad, const char *filename) {
    FILE *f = fopen(filename, "wb");
    if (!f) return;

    // Write temporary header
    fwrite(wad->header.identification, 1, 4, f);
    fwrite(&wad->header.numlumps, 4, 1, f);
    int32_t temp_ofs = 12;
    fwrite(&temp_ofs, 4, 1, f);

    // Write directory (temporary offsets)
    for (int i = 0; i < wad->header.numlumps; i++) {
        int32_t temp_offset = 0;
        fwrite(&temp_offset, 4, 1, f);
        fwrite(&wad->lumps[i].size, 4, 1, f);
        char name_padded[8] = {0};
        strncpy(name_padded, wad->lumps[i].name, 8);
        fwrite(name_padded, 1, 8, f);
    }

    // Write data and update offsets
    long dir_pos = ftell(f) - (wad->header.numlumps * 16);
    int32_t current_offset = 12 + (wad->header.numlumps * 16);
    for (int i = 0; i < wad->header.numlumps; i++) {
        wad->lumps[i].offset = current_offset;
        fwrite(wad->lumps[i].data, 1, wad->lumps[i].size, f);
        current_offset += wad->lumps[i].size;

        // Update directory entry
        fseek(f, dir_pos + i * 16, SEEK_SET);
        fwrite(&wad->lumps[i].offset, 4, 1, f);
        fseek(f, 0, SEEK_END);
    }

    fclose(f);
}

void wad_free(WADFile *wad) {
    for (int i = 0; i < wad->header.numlumps; i++) {
        free(wad->lumps[i].data);
    }
    free(wad->lumps);
    free(wad);
}

Usage example: WADFile *wad = wad_open("example.wad"); wad_print_properties(wad); wad_write(wad, "output.wad"); wad_free(wad);. This handles basic operations without compression support.