Assignment Chef icon Assignment Chef
All German tutorials

Programming lesson

FUSE-Dateisysteme und WAD-Dateiformat: Eine praktische Einführung mit DOOM-Mods

Lerne, wie du mit FUSE ein benutzerspace Dateisystem erstellst, das WAD-Dateien aus DOOM als Verzeichnisstruktur bereitstellt – ideal für Geheimdatenübertragung oder Retro-Gaming-Projekte.

FUSE Dateisystem WAD Dateiformat DOOM Modding Benutzerspace Dateisystem C++ FUSE Tutorial COP4600 Projekt WAD Bibliothek C++ FUSE Callbacks Lump Descriptor Namespace Marker Map Marker WAD Retro Gaming Dateisystem Dateisystem implementieren Geheimdatenübertragung DOOM FUSE Daemon WAD Dateien auslesen

Einleitung: Von DOOM-Mods zu versteckten Dateisystemen

Stell dir vor, du bist ein Doppelagent, der in einer feindlichen Organisation untergetaucht ist – und deine einzige Möglichkeit, Nachrichten nach draußen zu schmuggeln, sind die WAD-Dateien des Kultspiels DOOM. Genau das Szenario der aktuellen Aufgabe in COP4600: File Systems. Aber keine Sorge, du musst keine echten Geheimdienste unterstützen – stattdessen tauchst du in die Welt der FUSE (Filesystem in UserSpacE) ein und lernst, wie man ein Dateisystem im Benutzermodus bereitstellt, das auf dem WAD-Format basiert.

In diesem Tutorial zeige ich dir die Grundlagen des WAD-Formats, wie du es mit einer C++-Bibliothek ausliest und wie die FUSE-Schnittstelle funktioniert. Am Ende wirst du verstehen, wie du DOOM-Moddaten als normale Ordner und Dateien im Dateimanager anzeigen lassen kannst – und das alles, ohne den Kernel zu modifizieren. Das ist besonders nützlich, wenn du mit Retro-Gaming-Dateien arbeitest oder eigene Projekte mit benutzerdefinierten Dateisystemen umsetzen möchtest.

Das WAD-Format: Datenstruktur von DOOM

Das WAD (Where's All the Data) Format ist ein Container für Spielressourcen, das in DOOM und anderen klassischen Spielen verwendet wird. Es besteht aus drei Teilen: Header, Descriptors und Lumps. Lerne diese Struktur kennen, denn sie ist der Schlüssel zu deinem Dateisystem.

Header

Der Header enthält einen Magic-String (z.B. IWAD oder PWAD), die Anzahl der Descriptors und den Offset, an dem die Descriptor-Liste beginnt. Alle Zahlen sind im Little-Endian-Format gespeichert, was auf den meisten modernen Systemen bereits der Fall ist – du musst also keine Byte-Reihenfolge umkehren.

struct WadHeader {
    char magic[4];      // z.B. "IWAD"
    uint32_t numDescriptors;
    uint32_t descriptorOffset;
};

Descriptors

Jeder Descriptor beschreibt einen Lump (Datenblock) mit Offset, Länge und Namen. Manche Descriptoren haben spezielle Namenskonventionen, die sie als Marker für Verzeichnisse kennzeichnen.

struct WadDescriptor {
    uint32_t lumpOffset;
    uint32_t lumpSize;
    char name[8];       // Null-terminiert, aufgefüllt mit \0
};

Marker: Verzeichnisse im WAD

Es gibt zwei Arten von Markern: Map-Marker (z.B. E1M1) und Namespace-Marker (z.B. F1_START und F1_END). Map-Marker werden gefolgt von genau 10 Descriptoren, die zur Map gehören. Namespace-Marker umschließen einen Bereich von Descriptoren, die in einem Ordner mit dem Namen des Namespace (ohne Suffix) gruppiert werden.

Beispiel: Die Descriptoren F1_START, LUMP1, LUMP2, F1_END erzeugen einen Ordner F1 mit den Dateien LUMP1 und LUMP2.

Implementierung einer WAD-Bibliothek in C++

Bevor wir FUSE nutzen, brauchen wir eine Klasse, die WAD-Daten ausliest. Die Aufgabenstellung definiert eine Wad-Klasse mit folgenden Methoden:

  • static Wad* loadWad(const string &path): Lädt eine WAD-Datei und gibt einen Zeiger auf ein neues Wad-Objekt zurück.
  • string getMagic(): Gibt den Magic-String zurück.
  • bool isContent(const string &path): Prüft, ob der Pfad eine Datei (Lump) oder ein Verzeichnis ist.
  • bool isDirectory(const string &path): Prüft, ob der Pfad ein Verzeichnis ist.
  • int getSize(const string &path): Gibt die Größe eines Lumps zurück oder -1 bei Verzeichnissen.
  • int getContents(const string &path, char *buffer, int length, int offset): Kopiert Daten aus einem Lump in einen Buffer.

Die Bibliothek muss die Descriptor-Liste parsen und eine interne Verzeichnisstruktur aufbauen. Du kannst dazu eine Map von Pfaden zu Descriptor-Indizes verwenden. Achte darauf, dass Wurzelverzeichnis "/" ist und Pfade mit '/' getrennt werden.

FUSE: Ein Dateisystem im Benutzermodus

FUSE erlaubt es dir, ein Dateisystem zu implementieren, ohne Kernel-Module zu schreiben. Dein Programm wird ein Daemon, der sich bei FUSE registriert und Callback-Funktionen für Dateioperationen bereitstellt. Für die Aufgabe musst du mindestens getattr, readdir, open und read implementieren. Optional auch mkdir, mknod und write.

Ein typischer FUSE-Callback sieht so aus:

static int wad_getattr(const char *path, struct stat *stbuf) {
    memset(stbuf, 0, sizeof(struct stat));
    if (wad->isDirectory(path)) {
        stbuf->st_mode = S_IFDIR | 0755;
        stbuf->st_nlink = 2;
    } else if (wad->isContent(path)) {
        stbuf->st_mode = S_IFREG | 0444;
        stbuf->st_nlink = 1;
        stbuf->st_size = wad->getSize(path);
    } else {
        return -ENOENT;
    }
    return 0;
}

Mit FUSE kannst du dann das WAD-Dateisystem an einem Mountpunkt bereitstellen. Zum Beispiel: ./wad_fuse /mnt/wad. Anschließend siehst du im Dateimanager die Ordnerstruktur der WAD-Datei.

Praktische Anwendung: Geheimdaten in DOOM-Mods

Warum ist das gerade jetzt relevant? Im Jahr 2026 erleben Retro-Games eine Renaissance, und DOOM-Mods sind beliebter denn je. Viele Entwickler nutzen WAD-Dateien, um benutzerdefinierte Level und Texturen zu erstellen. Mit deinem FUSE-Dateisystem könntest du zum Beispiel versteckte Botschaften in Texturen einbetten – genau wie in der Aufgabenstellung. Aber auch für legitime Zwecke ist es nützlich: Du kannst WAD-Inhalte durchsuchen, extrahieren oder sogar neue Dateien hinzufügen, ohne spezielle Tools zu verwenden.

Ein Beispiel aus der Praxis: Du möchtest alle Texturen eines WAD-Archivs in einen Ordner kopieren. Mit dem gemounteten Dateisystem kannst du einfach cp /mnt/wad/F1/*.png ./textures/ ausführen – vorausgesetzt, die Lumps sind als PNG erkennbar. (In echtem DOOM sind sie meist in einem eigenen Format, aber das Prinzip bleibt.)

Herausforderungen und Tipps

  • Marker richtig behandeln: Achte darauf, dass Map-Marker genau 10 Descriptoren folgen und dass Namespace-Marker paarweise auftreten. Deine Bibliothek muss diese Struktur erkennen und die entsprechenden Verzeichnisse anlegen.
  • Schreibzugriff: In der Aufgabenstellung darfst du nicht in bestehende Lumps schreiben, aber du kannst neue Dateien (Lumps) anlegen. Das erfordert, dass du die WAD-Datei modifizierst – ein spannender Aspekt, der über reines Lesen hinausgeht.
  • Fehlerbehandlung: FUSE-Operationen müssen korrekte Fehlercodes zurückgeben (z.B. -ENOENT für nicht existierende Pfade). Teste dein Dateisystem mit ls -la, cat und touch im gemounteten Verzeichnis.

Fazit

Mit FUSE und einer guten WAD-Bibliothek hast du ein mächtiges Werkzeug, um alte Spieldateien als vollwertiges Dateisystem bereitzustellen. Ob für Geheimdatenübertragung (wie im Lizard-Legion-Szenario) oder für Retro-Gaming-Projekte – die Technik ist vielseitig einsetzbar. Probiere es selbst aus: Lade eine DOOM-WAD-Datei herunter (z.B. die Shareware-Version von DOOM) und implementiere die grundlegenden FUSE-Callbacks. Du wirst sehen, wie einfach es ist, ein eigenes Dateisystem zu bauen.

Und denk dran: Die Widerstandskämpfer zählen auf dich! Viel Erfolg bei deinem Projekt.