Cyotek Development Bloghttps://devblog.cyotek.com/tag/wad/atom.xml2020-07-31T08:23:25ZWriting DOOM WAD filesurn:uuid:54879631-44df-42e2-9eee-96318bc5e3342020-07-31T08:23:25Z2020-07-30T07:34:16Z<p>In a <a href="/post/reading-doom-wad-files">prior post</a>, I described id's WAD format used by
classic games such as DOOM and how to read them. This post
covers how to write them. As with my first post, this only
covers the original WAD format, not the enhanced ones which
followed.</p>
<h2 id="the-format">The Format</h2>
<p>A brief recap on the format. There is a 12 byte header which
details the wad type, the number of lumps of data it contains,
and an offset where the directory index is located.</p>
<table>
<thead>
<tr>
<th>Range</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>0</code> - <code>3</code></td>
<td>Either the string <code>IWAD</code> or <code>PWAD</code></td>
</tr>
<tr>
<td><code>4</code> - <code>7</code></td>
<td>The number of entries in the directory</td>
</tr>
<tr>
<td><code>8</code> - <code>11</code></td>
<td>The location of the directory</td>
</tr>
</tbody>
</table>
<p>The directory index is comprised of (16 * number of lumps) bytes
which describe the lumps. Each 16 byte header details the size,
the position in the data and the lump name.</p>
<table>
<thead>
<tr>
<th>Range</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>0</code> - <code>3</code></td>
<td>The location of the lump</td>
</tr>
<tr>
<td><code>4</code> - <code>7</code></td>
<td>The size of the lump</td>
</tr>
<tr>
<td><code>8</code> - <code>15</code></td>
<td>The name of the lump, padded with <code>NUL</code> bytes</td>
</tr>
</tbody>
</table>
<p>All integer values are in <a href="https://en.wikipedia.org/wiki/Endianness" rel="external nofollow noopener">little-endian</a> format.</p>
<h2 id="considerations">Considerations</h2>
<p>The nature of the WAD file means that in theory that you should
be able to make changes to it without having to rewrite the
entire file. For example, adding a new lump of data could simply
be added to the end of the existing data, overwriting the existing
directory index, and then a new index appended. Replacing a lump
with data the same size or small could overwrite the existing
lump, and adjusting the directory index entry. Even when
removing a lump you could opt to leave the data behind (or zero it out!) and
simply remove the meta data from the directory index.</p>
<p>However, the simplest means (albeit most inefficient) is to
rewrite the whole WAD. At this point I am simply exploring the
format (and its subsequent iterations) so therefore I'm not
going to go out of my way to complicate things and so this
demonstration program will recreate the WAD each time it is
changed.</p>
<h2 id="creating-a-wad-from-scratch">Creating a WAD From Scratch</h2>
<p>The previous post introduced the <code>WadReader</code> class, a way of
quickly enumerating a WAD file. Here, I introduce the
<code>WadOutputStream</code> class as a counterpart. This class can be used
to easy create a WAD file by calling <code>PutNextLump</code> with the name
of the lump to add, then write the lump data via the usual
<code>Stream</code> methods. Once done, flushing or closing the stream will
automatically write the directory entry. I borrowed this pattern
from <a href="https://github.com/haf/DotNetZip.Semverd" rel="external nofollow noopener">DotNetZip</a> as I find it a convenient way of creating
Zip files. In fact, I'll likely refactor <code>WadReader</code> at some
point to act more like <code>ZipInputStream</code> as well.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">using</span> <span class="symbol">(</span>Stream output <span class="symbol">=</span> File<span class="symbol">.</span>Create<span class="symbol">(</span><span class="string">&quot;test.wad&quot;</span><span class="symbol">)</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>WadOutputStream target <span class="symbol">=</span> <span class="keyword">new</span> WadOutputStream<span class="symbol">(</span>output<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>BinaryWriter writer <span class="symbol">=</span> <span class="keyword">new</span> BinaryWriter<span class="symbol">(</span>target<span class="symbol">,</span> Encoding<span class="symbol">.</span>UTF<span class="number">8</span><span class="symbol">,</span> <span class="keyword">true</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 target<span class="symbol">.</span>PutNextLump<span class="symbol">(</span><span class="string">&quot;PHOTO1&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 writer<span class="symbol">.</span>Write<span class="symbol">(</span>File<span class="symbol">.</span>ReadAllBytes<span class="symbol">(</span><span class="string">&quot;photo1.jpg&quot;</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 target<span class="symbol">.</span>PutNextLump<span class="symbol">(</span><span class="string">&quot;PHOTO2&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 writer<span class="symbol">.</span>Write<span class="symbol">(</span>File<span class="symbol">.</span>ReadAllBytes<span class="symbol">(</span><span class="string">&quot;photo2.jpg&quot;</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 target<span class="symbol">.</span>PutNextLump<span class="symbol">(</span><span class="string">&quot;PHOTO5&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 writer<span class="symbol">.</span>Write<span class="symbol">(</span>File<span class="symbol">.</span>ReadAllBytes<span class="symbol">(</span><span class="string">&quot;photo5.jpg&quot;</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>When creating an instance of <code>WadOutputStream</code>, it will immediately
write a placeholder header to the stream. This will have the correct WAD type, but the count and directory position will all be defaults until filled in at the end.</p>
<blockquote>
<p>As with the previous article, error handling, parameter
validation and non-essential code has been elided from these
snippets.</p>
</blockquote>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">class</span> WadOutputStream <span class="symbol">:</span> Stream
<span class="symbol">{</span>
 <span class="keyword">private</span> <span class="keyword">readonly</span> List<span class="symbol">&lt;</span>WadLump<span class="symbol">&gt;</span> _lumps<span class="symbol">;</span>
 <span class="keyword">private</span> <span class="keyword">readonly</span> Stream _output<span class="symbol">;</span>
 <span class="keyword">private</span> <span class="keyword">readonly</span> <span class="keyword">long</span> _start<span class="symbol">;</span>
 <span class="keyword">private</span> <span class="keyword">bool</span> _writtenDirectory<span class="symbol">;</span>

 <span class="keyword">public</span> WadOutputStream<span class="symbol">(</span>Stream output<span class="symbol">,</span> WadType type<span class="symbol">)</span>
 <span class="symbol">{</span>
 _output <span class="symbol">=</span> output<span class="symbol">;</span>
 _start <span class="symbol">=</span> output<span class="symbol">.</span>Position<span class="symbol">;</span>
 _lumps <span class="symbol">=</span> <span class="keyword">new</span> List<span class="symbol">&lt;</span>WadLump<span class="symbol">&gt;</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>WriteWadHeader<span class="symbol">(</span>type<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">private</span> <span class="keyword">void</span> WriteWadHeader<span class="symbol">(</span>WadType type<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">byte</span><span class="symbol">[</span><span class="symbol">]</span> buffer<span class="symbol">;</span>

 buffer <span class="symbol">=</span> <span class="keyword">new</span> <span class="keyword">byte</span><span class="symbol">[</span>WadConstants<span class="symbol">.</span>WadHeaderLength<span class="symbol">]</span><span class="symbol">;</span>

 buffer<span class="symbol">[</span><span class="number">0</span><span class="symbol">]</span> <span class="symbol">=</span> type <span class="symbol">==</span> WadType<span class="symbol">.</span>Internal <span class="symbol">?</span> <span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span><span class="string">&#39;I&#39;</span> <span class="symbol">:</span> <span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span><span class="string">&#39;P&#39;</span><span class="symbol">;</span>
 buffer<span class="symbol">[</span><span class="number">1</span><span class="symbol">]</span> <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span><span class="string">&#39;W&#39;</span><span class="symbol">;</span>
 buffer<span class="symbol">[</span><span class="number">2</span><span class="symbol">]</span> <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span><span class="string">&#39;A&#39;</span><span class="symbol">;</span>
 buffer<span class="symbol">[</span><span class="number">3</span><span class="symbol">]</span> <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span><span class="string">&#39;D&#39;</span><span class="symbol">;</span>
 <span class="comment">// positions 4 - 11 left at zero for now</span>

 _output<span class="symbol">.</span>Write<span class="symbol">(</span>buffer<span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> WadConstants<span class="symbol">.</span>WadHeaderLength<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
</pre>
</figure>
<p>Although the <code>WadOutputStream</code> inherits from <code>Stream</code>, you can't
just randomly write to it. Prior to writing a lump, you need to
call the <code>PutNextLump</code> method. This method both finalises the
previous lump, if applicable, and prepares the new lump. This is
required so that the class can keep track of the lumps in order
to write the directory entry at the end.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
 <span class="keyword">public</span> <span class="keyword">void</span> PutNextLump<span class="symbol">(</span><span class="keyword">string</span> name<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>FinaliseLump<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 _lumps<span class="symbol">.</span>Add<span class="symbol">(</span><span class="keyword">new</span> WadLump
 <span class="symbol">{</span>
 Name <span class="symbol">=</span> name<span class="symbol">,</span>
 Offset <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span>_output<span class="symbol">.</span>Position
 <span class="symbol">}</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">private</span> <span class="keyword">void</span> FinaliseLump<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>_lumps<span class="symbol">.</span>Count <span class="symbol">&gt;</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 WadLump lump<span class="symbol">;</span>

 lump <span class="symbol">=</span> _lumps<span class="symbol">[</span>_lumps<span class="symbol">.</span>Count <span class="symbol">-</span> <span class="number">1</span><span class="symbol">]</span><span class="symbol">;</span>
 lump<span class="symbol">.</span>Size <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span>_output<span class="symbol">.</span>Position <span class="symbol">-</span> lump<span class="symbol">.</span>Offset<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
</pre>
</figure>
<p>Finally, when we're done writing, we need to finish off the WAD
by writing the directory index and updating the header. We'll do
this by overriding both <code>Flush</code> and <code>Dispose</code>.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
 <span class="keyword">protected</span> <span class="keyword">override</span> <span class="keyword">void</span> Dispose<span class="symbol">(</span><span class="keyword">bool</span> disposing<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>disposing <span class="symbol">&amp;&amp;</span> <span class="symbol">!</span>_writtenDirectory<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>Flush<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">base</span><span class="symbol">.</span>Dispose<span class="symbol">(</span>disposing<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">void</span> Flush<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span><span class="symbol">!</span>_writtenDirectory<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>FinaliseLump<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>WriteDirectory<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 _output<span class="symbol">.</span>Flush<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
</pre>
</figure>
<p>When preparing for writing the directory, we again check to see
if there are any lumps and finalise the last one as we did when
adding a new lump.</p>
<p>Now we can finalise the file header. We do this by creating a
new 8 byte array and place the lump count in the first four
bytes, the current stream position into the latter four,
representing where the directory index will be written. We then
write these 8 bytes at the start of the stream, overwriting the
zero block written earlier.</p>
<p>To write an integer into the byte array I'm using a custom
<code>PutInt32Le</code> method. While the <a href="https://docs.microsoft.com/en-us/dotnet/api/system.bitconverter" rel="external nofollow noopener"><code>BitConverter</code></a> class has a
<code>GetBytes</code> method, firstly this will result in repeated
allocations from byte array creation, and secondly I'd then have
to copy the contents in the destination array. Finally,
<code>BitConverter</code> will return the results based on the endian-ness
of the system and we need to ensure that <a href="https://en.wikipedia.org/wiki/Endianness" rel="external nofollow noopener">little-endian</a> is
used.</p>
<p>Once the WAD header is updated we enumerate each of our lumps
and build the 16 byte directory header containing the lump
offset, size and the padded name and then write those at the end
of the file.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
 <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> PutInt<span class="number">32</span>Le<span class="symbol">(</span><span class="keyword">int</span> value<span class="symbol">,</span> <span class="keyword">byte</span><span class="symbol">[</span><span class="symbol">]</span> buffer<span class="symbol">,</span> <span class="keyword">int</span> offset<span class="symbol">)</span>
 <span class="symbol">{</span>
 buffer<span class="symbol">[</span>offset <span class="symbol">+</span> <span class="number">3</span><span class="symbol">]</span> <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span><span class="symbol">(</span><span class="symbol">(</span>value <span class="symbol">&amp;</span> <span class="number">0xFF000000</span><span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> <span class="number">24</span><span class="symbol">)</span><span class="symbol">;</span>
 buffer<span class="symbol">[</span>offset <span class="symbol">+</span> <span class="number">2</span><span class="symbol">]</span> <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span><span class="symbol">(</span><span class="symbol">(</span>value <span class="symbol">&amp;</span> <span class="number">0x00FF0000</span><span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> <span class="number">16</span><span class="symbol">)</span><span class="symbol">;</span>
 buffer<span class="symbol">[</span>offset <span class="symbol">+</span> <span class="number">1</span><span class="symbol">]</span> <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span><span class="symbol">(</span><span class="symbol">(</span>value <span class="symbol">&amp;</span> <span class="number">0x0000FF00</span><span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> <span class="number">8</span><span class="symbol">)</span><span class="symbol">;</span>
 buffer<span class="symbol">[</span>offset<span class="symbol">]</span> <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span><span class="symbol">(</span><span class="symbol">(</span>value <span class="symbol">&amp;</span> <span class="number">0x000000FF</span><span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> <span class="number">0</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">private</span> <span class="keyword">void</span> WriteDirectory<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">byte</span><span class="symbol">[</span><span class="symbol">]</span> buffer<span class="symbol">;</span>
 <span class="keyword">long</span> position<span class="symbol">;</span>

 buffer <span class="symbol">=</span> <span class="keyword">new</span> <span class="keyword">byte</span><span class="symbol">[</span>WadConstants<span class="symbol">.</span>DirectoryHeaderLength<span class="symbol">]</span><span class="symbol">;</span>
 position <span class="symbol">=</span> _output<span class="symbol">.</span>Position<span class="symbol">;</span>

 <span class="comment">// first update the header</span>
 WordHelpers<span class="symbol">.</span>PutInt<span class="number">32</span>Le<span class="symbol">(</span>_lumps<span class="symbol">.</span>Count<span class="symbol">,</span> buffer<span class="symbol">,</span> <span class="number">0</span><span class="symbol">)</span><span class="symbol">;</span>
 WordHelpers<span class="symbol">.</span>PutInt<span class="number">32</span>Le<span class="symbol">(</span><span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span>position<span class="symbol">,</span> buffer<span class="symbol">,</span> <span class="number">4</span><span class="symbol">)</span><span class="symbol">;</span>

 _output<span class="symbol">.</span>Position <span class="symbol">=</span> _start <span class="symbol">+</span> <span class="number">4</span><span class="symbol">;</span>
 _output<span class="symbol">.</span>Write<span class="symbol">(</span>buffer<span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> <span class="number">8</span><span class="symbol">)</span><span class="symbol">;</span>

 _output<span class="symbol">.</span>Position <span class="symbol">=</span> position<span class="symbol">;</span>

 <span class="comment">// now the directory entries</span>
 <span class="keyword">for</span> <span class="symbol">(</span><span class="keyword">int</span> i <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span> i <span class="symbol">&lt;</span> _lumps<span class="symbol">.</span>Count<span class="symbol">;</span> i<span class="symbol">++</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 WadLump lump<span class="symbol">;</span>

 lump <span class="symbol">=</span> _lumps<span class="symbol">[</span>i<span class="symbol">]</span><span class="symbol">;</span>

 WordHelpers<span class="symbol">.</span>PutInt<span class="number">32</span>Le<span class="symbol">(</span>lump<span class="symbol">.</span>Offset<span class="symbol">,</span> buffer<span class="symbol">,</span> WadConstants<span class="symbol">.</span>LumpStartOffset<span class="symbol">)</span><span class="symbol">;</span>
 WordHelpers<span class="symbol">.</span>PutInt<span class="number">32</span>Le<span class="symbol">(</span>lump<span class="symbol">.</span>Size<span class="symbol">,</span> buffer<span class="symbol">,</span> WadConstants<span class="symbol">.</span>LumpSizeOffset<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">for</span> <span class="symbol">(</span><span class="keyword">int</span> j <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span> j <span class="symbol">&lt;</span> lump<span class="symbol">.</span>Name<span class="symbol">.</span>Length<span class="symbol">;</span> j<span class="symbol">++</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 buffer<span class="symbol">[</span>WadConstants<span class="symbol">.</span>LumpNameOffset <span class="symbol">+</span> j<span class="symbol">]</span> <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span>lump<span class="symbol">.</span>Name<span class="symbol">[</span>j<span class="symbol">]</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">for</span> <span class="symbol">(</span><span class="keyword">int</span> j <span class="symbol">=</span> lump<span class="symbol">.</span>Name<span class="symbol">.</span>Length<span class="symbol">;</span> j <span class="symbol">&lt;</span> WadConstants<span class="symbol">.</span>LumpNameLength<span class="symbol">;</span> j<span class="symbol">++</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 buffer<span class="symbol">[</span>WadConstants<span class="symbol">.</span>LumpNameOffset <span class="symbol">+</span> j<span class="symbol">]</span> <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 _output<span class="symbol">.</span>Write<span class="symbol">(</span>buffer<span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> buffer<span class="symbol">.</span>Length<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 _writtenDirectory <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="rewriting-an-existing-wad">Rewriting an existing WAD</h2>
<p>This example, taken from the <code>WadFile</code> class, enumerates all
existing lumps and then writes them into a new stream. Although
not demonstrated here, it assumes the <code>GetInputStream</code> for a
given <code>WadLump</code> will return either the original data for
existing lumps, the modified data for existing lumps that have
been altered, or the data for new lumps.</p>
<p>As it can't write to the source stream whilst also reading from
it, it does all this to a temporary stream, and then, when done,
copies the contents of the temporary stream over the original
stream.</p>
<p>This isn't exactly the most efficient approach, but does avoid
all of the complexity of determining which parts of the file to
update, which parts to clear, keeping a list of changed items
for batch saving, etc. This is most likely something I will
investigate further in a future topic.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">void</span> Save<span class="symbol">(</span>Stream stream<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>Stream temp <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetTemporaryStream<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>WadOutputStream output <span class="symbol">=</span> <span class="keyword">new</span> WadOutputStream<span class="symbol">(</span>temp<span class="symbol">,</span> _type<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">for</span> <span class="symbol">(</span><span class="keyword">int</span> i <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span> i <span class="symbol">&lt;</span> _lumps<span class="symbol">.</span>Count<span class="symbol">;</span> i<span class="symbol">++</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 WadLump lump<span class="symbol">;</span>

 lump <span class="symbol">=</span> _lumps<span class="symbol">[</span>i<span class="symbol">]</span><span class="symbol">;</span>
 output<span class="symbol">.</span>PutNextLump<span class="symbol">(</span>lump<span class="symbol">.</span>Name<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">using</span> <span class="symbol">(</span>Stream input <span class="symbol">=</span> lump<span class="symbol">.</span>GetInputStream<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 input<span class="symbol">.</span>CopyTo<span class="symbol">(</span>output<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 output<span class="symbol">.</span>Flush<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 stream<span class="symbol">.</span>Position <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>
 stream<span class="symbol">.</span>SetLength<span class="symbol">(</span><span class="number">0</span><span class="symbol">)</span><span class="symbol">;</span>

 temp<span class="symbol">.</span>Position <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>
 temp<span class="symbol">.</span>CopyTo<span class="symbol">(</span>stream<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="to-pad-or-not-to-pad">To Pad, Or Not To Pad</h2>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/wad-writer-padding.png" class="gallery" title="An example of padding between lumps in DOOM.WAD, running under Mono on a 64bit Raspberry Pi" ><img src="https://images.cyotek.com/image/thumbnail/devblog/wad-writer-padding.png" alt="An example of padding between lumps in DOOM.WAD, running under Mono on a 64bit Raspberry Pi" decoding="async" loading="lazy" /></a><figcaption>An example of padding between lumps in DOOM.WAD, running under Mono on a 64bit Raspberry Pi</figcaption></figure>
<p>After I discovered that the data in the <a href="/post/decoding-doom-picture-files#padding">DOOM picture lumps were
padded</a>, I was curious if the WAD file itself was. I copied
the hex viewer project from that solution and used it to
highlight the different ranges in a WAD. To my surprise, it
seemed that in even though picture lumps already had their own
padding, the lumps themselves were also padded to always have an
even number of bytes. Interestingly, sometimes if a lump started
on an even number two padding bytes were still included. I
suppose there is a reason but I didn't dig further info it and
so didn't build padding support into the writer classes.</p>
<p>Also possibly worthy of note, I checked the <code>DARKWAR.WAD</code> file and
this didn't use padding at all.</p>
<h2 id="does-it-work">Does it work?</h2>
<p>In a word, yes. I tested dumping <code>DOOM.WAD</code> into separate data
files using the <code>waddemo</code> program, then repacking them into a
brand new WAD. I then ran DOOM using the new wad and played
through the first level. Everything seemed to be running fine.</p>
<h2 id="getting-the-source-code">Getting the source code</h2>
<p>As noted in the first article in this series, there isn't a
single download available per post as I've done a
larger-than-usual demonstration solution. The full project is
available from our <a href="https://github.com/cyotek/WadDemo" rel="external nofollow noopener">GitHub</a> page.</p>

<p><small>
All content <a href="https://devblog.cyotek.com/copyright-and-trademarks">Copyright (c) by Cyotek Ltd</a> or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.<br />Original URL of this content is https://devblog.cyotek.com/post/writing-doom-wad-files .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comReading DOOM WAD Filesurn:uuid:d3b426c8-4073-46e7-a5f9-0deb5c3386152020-07-30T07:32:57Z2020-07-30T07:32:57Z<p>WAD &quot;Where's All the Data&quot; files used by DOOM and various other
games are simple containers, similar to zip and other archive
formats, without additional complexity (such as compression) and
data-centric rather than file. This article describes how to
read the WAD files used by DOOM, DOOM II, Rise of the Triad and
similar games of that era. Yes, I'm talking DOS and 1993, not
the more modern reboots.</p>
<p>The article only covers reading of a WAD and extracting its
contents, it does not cover the format of the individual data
within given that the data is application dependent. With that
said, I'll be covering the DOOM picture format in the next
article.</p>
<p>In 2018 I looked into the MIX format used by the Command &amp;
Conquer games which is very similar to WAD but for reasons I
don't recall I didn't end up writing a post about the format.
Recently I finished reading Jimmy Maher's excellent <a href="https://www.filfre.net/tag/doom/" rel="external nofollow noopener">series on
DOOM</a> and that reminded me I had wanted to look into WAD and
other container formats for my own future use. As I have been
completely unable to finish a single draft blog post I currently
have, I decided something fresh and new (to me anyway!) was a
good idea.</p>
<p>Although I don't normally plug other sites, Jimmy's blog <a href="https://www.filfre.net/" rel="external nofollow noopener">The
Digital Antiquarian</a> is a fantastic blog of the games of
yesteryear and I wish I could write half as well as him.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/wad-reader-triad.png" class="gallery" title="An example of reading a WAD file and examining one of the contained data lumps" ><img src="https://images.cyotek.com/image/thumbnail/devblog/wad-reader-triad.png" alt="An example of reading a WAD file and examining one of the contained data lumps" decoding="async" loading="lazy" /></a><figcaption>An example of reading a WAD file and examining one of the contained data lumps</figcaption></figure><h2 id="about-wad-formats">About WAD Formats</h2>
<p>There are various formats of WAD file available, each building
on the previous. This initial series of articles only covers the
original version first introduced in DOOM. At the time of
writing, I haven't looked the other versions but I plan to look
at some of them in future articles.</p>
<p>I have tested the code presented in this article with WAD files
from Shareware DOOM, ULTIMATE DOOM, DOOM II and Rise of the
Triad: Dark War.</p>
<h2 id="the-format">The Format</h2>
<p>The format is simple enough. There is a 12 byte header which
details the wad type, the number of lumps of data it contains,
and an offset where the directory index is located.</p>
<table>
<thead>
<tr>
<th>Range</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>0</code> - <code>3</code></td>
<td>Either the string <code>IWAD</code> or <code>PWAD</code></td>
</tr>
<tr>
<td><code>4</code> - <code>7</code></td>
<td>The number of entries in the directory</td>
</tr>
<tr>
<td><code>8</code> - <code>11</code></td>
<td>The location of the directory</td>
</tr>
</tbody>
</table>
<p>The directory index is comprised of (16 * number of lumps) bytes
which describe the lumps. Each 16 byte header details the size,
the position in the data and the lump name.</p>
<table>
<thead>
<tr>
<th>Range</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>0</code> - <code>3</code></td>
<td>The location of the lump</td>
</tr>
<tr>
<td><code>4</code> - <code>7</code></td>
<td>The size of the lump</td>
</tr>
<tr>
<td><code>8</code> - <code>15</code></td>
<td>The name of the lump, padded with <code>NUL</code> bytes</td>
</tr>
</tbody>
</table>
<p>As far as I know, the directory can be located anywhere in a WAD
file, or at least anywhere after the header. All of the WADs I
have examined have the directory at the end of the file which
makes perfect sense from a serialisation standpoint, but there's
no reason why it couldn't be elsewhere. The only rule is that
all elements in the directory index must be contiguous.</p>
<p>All integer values are in <a href="https://en.wikipedia.org/wiki/Endianness" rel="external nofollow noopener">little-endian</a> format.</p>
<h3 id="wad-types">WAD Types</h3>
<p>The first four bytes of the file header are either <code>IWAD</code> or
<code>PWAD</code>, and this denotes the type of the WAD. The <code>I</code> prefix
means this is an &quot;internal&quot; WAD, which is the main WAD for a
game. The <code>P</code> prefix denotes a &quot;patch&quot; WAD, which allows a WAD
to override the lumps from the main internal WAD, e.g. for
providing custom levels, skins or other data.</p>
<h2 id="reading-the-header">Reading the Header</h2>
<p>Reading the header is quite straightforward - first read in the
12 bytes into a buffer and define the WAD type based on the
first byte. Next, we extract 32bit integers from each set of 4
bytes in the remainder of the header that contain the number of
data lumps and then the start of the directory listing.</p>
<blockquote>
<p>Note: In the interests of clarity, parameter and data
validation have been omitted from the snippets in this
article.</p>
</blockquote>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">const</span> <span class="keyword">byte</span> _wadHeaderLength <span class="symbol">=</span> <span class="number">12</span><span class="symbol">;</span>
<span class="keyword">private</span> <span class="keyword">const</span> <span class="keyword">byte</span> _lumpCountOffset <span class="symbol">=</span> <span class="number">4</span><span class="symbol">;</span>
<span class="keyword">private</span> <span class="keyword">const</span> <span class="keyword">byte</span> _directoryStartOffset <span class="symbol">=</span> <span class="number">8</span><span class="symbol">;</span>

<span class="keyword">private</span> WadType _type<span class="symbol">;</span>
<span class="keyword">private</span> <span class="keyword">int</span> _lumpCount<span class="symbol">;</span>
<span class="keyword">private</span> <span class="keyword">int</span> _directoryStart<span class="symbol">;</span>

<span class="keyword">private</span> <span class="keyword">void</span> ReadWadHeader<span class="symbol">(</span>Stream stream<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">byte</span><span class="symbol">[</span><span class="symbol">]</span> buffer<span class="symbol">;</span>

 buffer <span class="symbol">=</span> <span class="keyword">new</span> <span class="keyword">byte</span><span class="symbol">[</span>_wadHeaderLength<span class="symbol">]</span><span class="symbol">;</span>

 stream<span class="symbol">.</span>Read<span class="symbol">(</span>buffer<span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> _wadHeaderLength<span class="symbol">)</span><span class="symbol">;</span>

 _type <span class="symbol">=</span> _buffer<span class="symbol">[</span><span class="number">0</span><span class="symbol">]</span> <span class="symbol">==</span> <span class="string">&#39;I&#39;</span> <span class="symbol">?</span> WadType<span class="symbol">.</span>Internal <span class="symbol">:</span> WadType<span class="symbol">.</span>Patch<span class="symbol">;</span>

 _lumpCount <span class="symbol">=</span> GetInt<span class="number">32</span>Le<span class="symbol">(</span>_buffer<span class="symbol">,</span> _lumpCountOffset<span class="symbol">)</span><span class="symbol">;</span>
 _directoryStart <span class="symbol">=</span> GetInt<span class="number">32</span>Le<span class="symbol">(</span>_buffer<span class="symbol">,</span> _directoryStartOffset<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span> GetInt<span class="number">32</span>Le<span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">[</span><span class="symbol">]</span> buffer<span class="symbol">,</span> <span class="keyword">int</span> offset<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">return</span> buffer<span class="symbol">[</span>offset <span class="symbol">+</span> <span class="number">3</span><span class="symbol">]</span> <span class="symbol">&lt;&lt;</span> <span class="number">24</span> <span class="symbol">|</span> buffer<span class="symbol">[</span>offset <span class="symbol">+</span> <span class="number">2</span><span class="symbol">]</span> <span class="symbol">&lt;&lt;</span> <span class="number">16</span> <span class="symbol">|</span> buffer<span class="symbol">[</span>offset <span class="symbol">+</span> <span class="number">1</span><span class="symbol">]</span> <span class="symbol">&lt;&lt;</span> <span class="number">8</span> <span class="symbol">|</span> buffer<span class="symbol">[</span>offset<span class="symbol">]</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<blockquote>
<p>You could use the <code>BitConverter.ToInt32</code> method, but then if
this code was ran on a big-endian system, the
<a href="https://docs.microsoft.com/en-us/dotnet/api/system.bitconverter" rel="external nofollow noopener">BitConverter</a> class would automatically reverse the bytes,
returning values that would be <em>very</em> wrong and so this set of
articles will use their own code which ignores the endian-ness
of the system and will always read and write as little-endian.</p>
</blockquote>
<h2 id="reading-the-directory">Reading the Directory</h2>
<p>Now that we know where the directory index is located in the
file, we can read out the individual lump details. As with the
WAD header, we declare a buffer big enough to fill the directory
header, then read in the bytes. Using the same <code>GetInt32Le</code>
method described earlier, we extract the size of the lump and
its position in the file.</p>
<p>Next, we find the real length of the lump name, by starting at
the end of the array and working back until we find a non-zero
value. Once we have this length we call
<code>Encoding.ASCII.GetString</code> to extract the name. Unfortunately,
if we called this API without defining the true length, the
returned string would include any<code>NUL</code> padding bytes.</p>
<figure class="lang-c# highlight"><figcaption><span>c#</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">const</span> <span class="keyword">byte</span> _directoryHeaderLength <span class="symbol">=</span> <span class="number">16</span><span class="symbol">;</span>
<span class="keyword">private</span> <span class="keyword">const</span> <span class="keyword">byte</span> _lumpStartOffset <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>
<span class="keyword">private</span> <span class="keyword">const</span> <span class="keyword">byte</span> _lumpSizeOffset <span class="symbol">=</span> <span class="number">4</span><span class="symbol">;</span>
<span class="keyword">private</span> <span class="keyword">const</span> <span class="keyword">byte</span> _lumpNameOffset <span class="symbol">=</span> <span class="number">8</span><span class="symbol">;</span>

<span class="keyword">private</span> <span class="keyword">void</span> LoadDirectory<span class="symbol">(</span>Stream stream<span class="symbol">,</span> <span class="keyword">int</span> lumpCount<span class="symbol">,</span> <span class="keyword">int</span> directoryStart<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">byte</span><span class="symbol">[</span><span class="symbol">]</span> buffer<span class="symbol">;</span>

 stream<span class="symbol">.</span>Seek<span class="symbol">(</span>directoryStart<span class="symbol">,</span> SeekOrigin<span class="symbol">.</span>Begin<span class="symbol">)</span><span class="symbol">;</span>

 buffer <span class="symbol">=</span> <span class="keyword">new</span> <span class="keyword">byte</span><span class="symbol">[</span>_directoryHeaderLength<span class="symbol">]</span><span class="symbol">;</span>

 <span class="keyword">for</span> <span class="symbol">(</span><span class="keyword">int</span> i <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span> i <span class="symbol">&lt;</span> lumpCount<span class="symbol">;</span> i<span class="symbol">++</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> offset<span class="symbol">;</span>
 <span class="keyword">int</span> size<span class="symbol">;</span>
 <span class="keyword">string</span> name<span class="symbol">;</span>

 stream<span class="symbol">.</span>Read<span class="symbol">(</span>buffer<span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> _directoryHeaderLength<span class="symbol">)</span><span class="symbol">;</span>

 offset <span class="symbol">=</span> GetInt<span class="number">32</span>Le<span class="symbol">(</span>buffer<span class="symbol">,</span> _lumpStartOffset<span class="symbol">)</span><span class="symbol">;</span>
 size <span class="symbol">=</span> GetInt<span class="number">32</span>Le<span class="symbol">(</span>buffer<span class="symbol">,</span> _lumpSizeOffset<span class="symbol">)</span><span class="symbol">;</span>
 name <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetSafeLumpName<span class="symbol">(</span>buffer<span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// Do something with the 3 values</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>

<span class="keyword">private</span> <span class="keyword">string</span> GetSafeLumpName<span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">[</span><span class="symbol">]</span> buffer<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">int</span> length<span class="symbol">;</span>

 length <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>

 <span class="keyword">for</span> <span class="symbol">(</span><span class="keyword">int</span> i <span class="symbol">=</span> _directoryHeaderLength<span class="symbol">;</span> i <span class="symbol">&gt;</span> _lumpNameOffset<span class="symbol">;</span> i<span class="symbol">--</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>entry<span class="symbol">[</span>i <span class="symbol">-</span> <span class="number">1</span><span class="symbol">]</span> <span class="symbol">!=</span> <span class="string">&#39;\0&#39;</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 length <span class="symbol">=</span> i <span class="symbol">-</span> _lumpNameOffset<span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> length <span class="symbol">&gt;</span> <span class="number">0</span>
 <span class="symbol">?</span> Encoding<span class="symbol">.</span>ASCII<span class="symbol">.</span>GetString<span class="symbol">(</span>entry<span class="symbol">,</span> _lumpNameOffset<span class="symbol">,</span> length<span class="symbol">)</span>
 <span class="symbol">:</span> <span class="keyword">null</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h3 id="about-names-and-empty-data">About Names and Empty Data</h3>
<p>Lump names may not be unique and can appear multiple times. For
example, every DOOM map that I've looked at so far has a lump
named <code>THINGS</code>, another named <code>LINEDEFS</code> and several more.</p>
<p>As a result, DOOM seems to make use of a uniquely named lump
(e.g. <code>E1M1</code>) that serve no purpose other than to be a bookmark
to a contiguous set of lumps that make up a feature (and
sometimes another placeholder at the end if the lumps are
dynamic). For placeholders, the lump size is set to zero, and
the lump offset is either set to the offset of the next valid
lump or again zero. This also means that, depending on the
application using the WAD, lump order is important.</p>
<h2 id="reading-lump-data">Reading Lump Data</h2>
<p>To read the actual data for a given lump, we would set the
<code>Position</code> of our backing <code>Stream</code> to the lump offset and then
only read data up to the length of the lump.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
 <span class="keyword">using</span> <span class="symbol">(</span>Stream stream <span class="symbol">=</span> File<span class="symbol">.</span>OpenRead<span class="symbol">(</span>fileName<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>WadReader reader <span class="symbol">=</span> <span class="keyword">new</span> WadReader<span class="symbol">(</span>stream<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 WadLump lump<span class="symbol">;</span>

 <span class="keyword">while</span> <span class="symbol">(</span><span class="symbol">(</span>lump <span class="symbol">=</span> reader<span class="symbol">.</span>GetNextLump<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span> <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">byte</span><span class="symbol">[</span><span class="symbol">]</span> buffer<span class="symbol">;</span>

 buffer <span class="symbol">=</span> <span class="keyword">new</span> <span class="keyword">byte</span><span class="symbol">[</span>lump<span class="symbol">.</span>Size<span class="symbol">]</span><span class="symbol">;</span>

 stream<span class="symbol">.</span>Position <span class="symbol">=</span> lump<span class="symbol">.</span>Offset<span class="symbol">;</span>
 stream<span class="symbol">.</span>Read<span class="symbol">(</span>buffer<span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> buffer<span class="symbol">.</span>Length<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
</pre>
</figure>
<p>This sounds error prone and means you have to know this
information up front instead of being able to pass a <code>Stream</code> to
another method. So for this case, I created an <code>OffsetStream</code>
class which basically acts as a window into another stream
without being to read data it shouldn't or the caller needing to
explicitly know about source boundaries.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">internal</span> <span class="keyword">sealed</span> <span class="keyword">class</span> OffsetStream <span class="symbol">:</span> Stream
<span class="symbol">{</span>
 <span class="keyword">private</span> <span class="keyword">readonly</span> <span class="keyword">int</span> _length<span class="symbol">;</span>
 <span class="keyword">private</span> <span class="keyword">readonly</span> <span class="keyword">int</span> _start<span class="symbol">;</span>
 <span class="keyword">private</span> <span class="keyword">readonly</span> Stream _stream<span class="symbol">;</span>
 <span class="keyword">private</span> <span class="keyword">long</span> _position<span class="symbol">;</span>

 <span class="keyword">public</span> OffsetStream<span class="symbol">(</span>Stream source<span class="symbol">,</span> <span class="keyword">int</span> start<span class="symbol">,</span> <span class="keyword">int</span> length<span class="symbol">)</span>
 <span class="symbol">{</span>
 _stream <span class="symbol">=</span> source<span class="symbol">;</span>
 _start <span class="symbol">=</span> start<span class="symbol">;</span>
 _length <span class="symbol">=</span> length<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">bool</span> CanRead
 <span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> <span class="keyword">true</span><span class="symbol">;</span> <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">bool</span> CanSeek
 <span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> <span class="keyword">true</span><span class="symbol">;</span> <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">bool</span> CanWrite
 <span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> <span class="keyword">false</span><span class="symbol">;</span> <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">long</span> Length
 <span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _length<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">long</span> Position
 <span class="symbol">{</span>
 <span class="keyword">get</span> <span class="symbol">{</span> <span class="keyword">return</span> _position<span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">set</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>value <span class="symbol">&lt;</span> <span class="number">0</span> <span class="symbol">||</span> value <span class="symbol">&gt;</span> _length<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> ArgumentOutOfRangeException<span class="symbol">(</span>nameof<span class="symbol">(</span>value<span class="symbol">)</span><span class="symbol">,</span> value<span class="symbol">,</span> <span class="string">&quot;Value outside of stream range.&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 _position <span class="symbol">=</span> value<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">int</span> Read<span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">[</span><span class="symbol">]</span> buffer<span class="symbol">,</span> <span class="keyword">int</span> offset<span class="symbol">,</span> <span class="keyword">int</span> count<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>_position <span class="symbol">+</span> count <span class="symbol">&gt;</span> _length<span class="symbol">)</span>
 <span class="symbol">{</span>
 count <span class="symbol">=</span> _length <span class="symbol">-</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span>_position<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">if</span> <span class="symbol">(</span>count <span class="symbol">&gt;</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 _stream<span class="symbol">.</span>Position <span class="symbol">=</span> _start <span class="symbol">+</span> _position<span class="symbol">;</span>
 _stream<span class="symbol">.</span>Read<span class="symbol">(</span>buffer<span class="symbol">,</span> offset<span class="symbol">,</span> count<span class="symbol">)</span><span class="symbol">;</span>
 _position <span class="symbol">+=</span> count<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> count<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">public</span> <span class="keyword">override</span> <span class="keyword">long</span> Seek<span class="symbol">(</span><span class="keyword">long</span> offset<span class="symbol">,</span> SeekOrigin origin<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">long</span> value<span class="symbol">;</span>

 <span class="keyword">switch</span> <span class="symbol">(</span>origin<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">case</span> SeekOrigin<span class="symbol">.</span>Begin<span class="symbol">:</span>
 value <span class="symbol">=</span> offset<span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>

 <span class="keyword">case</span> SeekOrigin<span class="symbol">.</span>Current<span class="symbol">:</span>
 value <span class="symbol">=</span> _position <span class="symbol">+</span> offset<span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>

 <span class="keyword">case</span> SeekOrigin<span class="symbol">.</span>End<span class="symbol">:</span>
 value <span class="symbol">=</span> _length <span class="symbol">-</span> offset<span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>

 <span class="keyword">default</span><span class="symbol">:</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> ArgumentOutOfRangeException<span class="symbol">(</span>nameof<span class="symbol">(</span>origin<span class="symbol">)</span><span class="symbol">,</span> origin<span class="symbol">,</span> <span class="string">&quot;Invalid origin value.&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">this</span><span class="symbol">.</span>Position <span class="symbol">=</span> value<span class="symbol">;</span>

 <span class="keyword">return</span> value<span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>With this class in place, I can now get a <code>Stream</code> that only
provides access to the a specific lumps data with a call similar
to the below.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> Stream GetInputStream<span class="symbol">(</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">return</span> <span class="keyword">new</span> OffsetStream<span class="symbol">(</span>_container<span class="symbol">,</span> _offset<span class="symbol">,</span> _size<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>I can then dispose of this stream or pass it to another method
(for example <code>ImageFile.FromStream</code>) without needing to know or
care that this is part of something bigger or affecting that.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">while</span> <span class="symbol">(</span><span class="symbol">(</span>lump <span class="symbol">=</span> reader<span class="symbol">.</span>GetNextLump<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span> <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
<span class="symbol">{</span>
 Image image <span class="symbol">=</span> Image<span class="symbol">.</span>FromStream<span class="symbol">(</span>lump<span class="symbol">.</span>GetInputStream<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="putting-it-all-together">Putting it all together</h2>
<p>For this example, I created the <code>WadReader</code> class, which is a
forward reading class for quickly enumerating the contents of a
WAD. I also added a <code>WadFile</code> class which will load all the lump
meta data into a collection for further use.</p>
<h3 id="using-the-wadreader">Using the WadReader</h3>
<p>The <code>WadReader</code> is designed for quickly enumerating the contents
of a WAD. It maintains enough state to know where it is in the
WAD, but nothing else, expecting the consumer to take care of
storing whatever information is required. This would be useful,
for example, if you wanted to pull out one or more lumps for
load on demand.</p>
<p>The <code>WadReader</code> class exposes a <code>Type</code> and <code>Count</code> property, and
a <code>GetNextLump</code> method which can be used to enumerate.
<code>GetNextLump</code> will return a valid object as long as there are
items remaining, and <code>null</code> once it reaches the end of the file.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> WriteWadInfo<span class="symbol">(</span><span class="keyword">string</span> fileName<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>Stream stream <span class="symbol">=</span> File<span class="symbol">.</span>OpenRead<span class="symbol">(</span>fileName<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">using</span> <span class="symbol">(</span>WadReader reader <span class="symbol">=</span> <span class="keyword">new</span> WadReader<span class="symbol">(</span>stream<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 WadLump lump<span class="symbol">;</span>

 Console<span class="symbol">.</span>WriteLine<span class="symbol">(</span><span class="string">&quot;File: {0}&quot;</span><span class="symbol">,</span> fileName<span class="symbol">)</span><span class="symbol">;</span>
 Console<span class="symbol">.</span>WriteLine<span class="symbol">(</span><span class="string">&quot;Type: {0}&quot;</span><span class="symbol">,</span> reader<span class="symbol">.</span>Type<span class="symbol">)</span><span class="symbol">;</span>
 Console<span class="symbol">.</span>WriteLine<span class="symbol">(</span><span class="string">&quot;Lump Count: {0}&quot;</span><span class="symbol">,</span> reader<span class="symbol">.</span>Count<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">while</span> <span class="symbol">(</span><span class="symbol">(</span>lump <span class="symbol">=</span> reader<span class="symbol">.</span>GetNextLump<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span> <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 Console<span class="symbol">.</span>WriteLine<span class="symbol">(</span><span class="string">&quot;{0}: Offset {1}, Size {2}&quot;</span><span class="symbol">,</span> lump<span class="symbol">.</span>Name<span class="symbol">,</span> lump<span class="symbol">.</span>Offset<span class="symbol">,</span> lump<span class="symbol">.</span>Size<span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// stream.Position is also automatically set to the</span>
 <span class="comment">// start of the lump data, allowing me to do</span>
 <span class="comment">// stream.Read if required, or call lump.GetInputStream()</span>
 <span class="comment">// to get a stream to pass to other methods</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h3 id="using-the-wadfile-class">Using the WadFile class</h3>
<p>The <code>WadFile</code> class loads all the lumps (but not the actual
data) into a collection so that it is always available. You can
then pull out lump data at any point without having to re-read
the directory and provides convenience methods for more easily
pulling out WAD data. It isn't as efficient as <code>WadReader</code>, but
easier to use. It also supports write operations whilst
<code>WadReader</code> does not.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> FillItems<span class="symbol">(</span><span class="keyword">string</span> fileName<span class="symbol">)</span>
<span class="symbol">{</span>
 WadFile wadFile<span class="symbol">;</span>

 wadFile <span class="symbol">=</span> WadFile<span class="symbol">.</span>LoadFrom<span class="symbol">(</span>fileName<span class="symbol">)</span><span class="symbol">;</span>

 namesListBox<span class="symbol">.</span>BeginUpdate<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 namesListBox<span class="symbol">.</span>Items<span class="symbol">.</span>Clear<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 namesListBox<span class="symbol">.</span>Sorted <span class="symbol">=</span> <span class="keyword">false</span><span class="symbol">;</span>

 <span class="keyword">for</span> <span class="symbol">(</span><span class="keyword">int</span> i <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span> i <span class="symbol">&lt;</span> wadFile<span class="symbol">.</span>Lumps<span class="symbol">.</span>Count<span class="symbol">;</span> i<span class="symbol">++</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 namesListBox<span class="symbol">.</span>Items<span class="symbol">.</span>Add<span class="symbol">(</span>wadFile<span class="symbol">.</span>Lumps<span class="symbol">[</span>i<span class="symbol">]</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">if</span> <span class="symbol">(</span>_useNameSort<span class="symbol">)</span>
 <span class="symbol">{</span>
 namesListBox<span class="symbol">.</span>Sorted <span class="symbol">=</span> <span class="keyword">true</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 namesListBox<span class="symbol">.</span>EndUpdate<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="wheres-all-the-source-code">Where's All The Source Code</h2>
<p>There is no single download available for this sample as rather
than doing a simple demo as I do for most blog posts, it is a
slightly more complex solution covering reading, writing and
various other features too. The full project is available from
our <a href="https://github.com/cyotek/WadDemo" rel="external nofollow noopener">GitHub</a> page.</p>
<h2 id="wrapping-up">Wrapping Up</h2>
<p>The WAD format has no real features and so is simple to read and
write. The linked GitHub page includes a demonstration program
which allows WAD files to be opened and contents extracted.</p>

<p><small>
All content <a href="https://devblog.cyotek.com/copyright-and-trademarks">Copyright (c) by Cyotek Ltd</a> or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.<br />Original URL of this content is https://devblog.cyotek.com/post/reading-doom-wad-files .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.com