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:
- 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).
- 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).
- 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).
- Lump Addressing: Data lumps are stored at absolute file offsets specified in the directory, allowing for potential overlaps or shared offsets among lumps.
- 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).
- Integer Type: All multi-byte numerical values are 4-byte signed integers.
- 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).
- 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).
- 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.
- Map Lump Ordering: For map data, lumps must follow a fixed sequence after the map name lump (e.g., "THINGS", "LINEDEFS", "SIDEDEFS").
- Extensibility: The format supports arbitrary lump types and contents, enabling use beyond original games.
- No Built-in Security: Absence of encryption, hashing, or integrity checks; data is stored in raw form.
- 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.
2. Two Direct Download Links for .WAD Files
The following are direct download links to sample .WAD files from publicly available sources:
- https://www.pc-freak.net/files/doom-wad-files/Plutonia.wad (Plutonia.wad from Final Doom)
- https://www.pc-freak.net/files/doom-wad-files/Tnt.wad (TNT.wad from Final Doom)
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.
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.