<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Binary — Tyler Elton</title>
    <link>https://tylerelton.com/tags/binary/</link>
    <description>Senior software engineer. Building in Go and Odin.</description>
    <language>en-us</language>
    <lastBuildDate>Sun, 08 Nov 2026 16:00:29 -0500</lastBuildDate>
    <generator>Hugo</generator>
    
    <atom:link href="https://tylerelton.com/tags/binary/index.xml" rel="self" type="application/rss&#43;xml" />
    
    
    <item>
      <title>Decoding REXPaint Files in Odin</title>
      <link>https://tylerelton.com/posts/decoding-rexpaint-files-in-odin/</link>
      <pubDate>Sun, 08 Nov 2026 00:00:00 +0000</pubDate>
      <dc:creator>Tyler Elton</dc:creator>
      <guid>https://tylerelton.com/posts/decoding-rexpaint-files-in-odin/</guid>
      <content:encoded><![CDATA[<p><a href="https://www.gridsagegames.com/rexpaint/">REXPaint</a> is an ASCII art tool by Kyzrati, the developer behind the fantastic roguelike <a href="https://www.gridsagegames.com/cogmind/">Cogmind</a>. You can use it to design UI, full-screen art, and level prefabs, which makes it a great fit for my game, <strong>Fimbul</strong>. In this post, we&rsquo;ll walk through decoding REXPaint&rsquo;s <code>.xp</code> files. If you haven&rsquo;t worked with binary file formats before, this is a gentle one to start with!</p>
<p>Grid Sage Games publishes the official REXPaint manual as plaintext<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, and Steve Landey hosts a more readable HTML version<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>. I&rsquo;ll cite the HTML version throughout this post because its sections have direct links. Everything here is based on REXPaint v1.70.</p>
<div class="alert alert-note" role="note">
  <p class="alert-title">Note</p>
  <p>We&rsquo;ll be using <a href="https://odin-lang.org/">the Odin programming language</a> throughout this post. We won&rsquo;t go over <em>every</em> piece of syntax, but I&rsquo;ll try to explain the more advanced sections of the code. For a more complete overview of the language, check out <a href="https://odin-lang.org/docs/demo/"><code>demo.odin</code></a>.</p>
</div><h2 id="prerequisites">Prerequisites</h2>
<p>To follow along, you&rsquo;ll need the Odin compiler. I&rsquo;m using version <code>dev-2026-10</code>. The snippets below build up an <code>xp</code> package in its own directory, next to a <code>main.odin</code> that uses it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">.
</span></span><span class="line"><span class="cl">├── main.odin
</span></span><span class="line"><span class="cl">├── test.xp
</span></span><span class="line"><span class="cl">└── xp
</span></span><span class="line"><span class="cl">    ├── decode.odin
</span></span><span class="line"><span class="cl">    └── xp.odin
</span></span></code></pre></div><p>Each snippet shows the imports it adds, but they belong at the top of the file.</p>
<div class="alert alert-warning" role="note">
  <p class="alert-title">Warning</p>
  <p>The latest version, <code>dev-2026-11</code>, overhauls the <code>core:compress</code> package and deprecates a few things, including <code>core:compress/gzip</code>, which this post uses. That version isn&rsquo;t on nixpkgs-unstable yet (I&rsquo;m using NixOS btw). Once it is, I&rsquo;ll update this post with the new code.</p>
</div><h2 id="step-0-the-format">Step 0: The Format</h2>
<p>If we look at a REXPaint file with <code>xxd</code>, we can see that it&rsquo;s gzipped:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ xxd ./assets/screens/title.xp <span class="p">|</span> head -1
</span></span><span class="line"><span class="cl">00000000: 1f8b <span class="m">0800</span> <span class="m">0000</span> <span class="m">0000</span> 000b ed9c db6d c330  .............m.0
</span></span></code></pre></div><p>The first two bytes, <code>1f8b</code>, are the magic number, a fixed byte sequence that identifies a file&rsquo;s format. Every gzipped file starts with it. Next comes <code>08</code>, the compression method. RFC 1952 defines only one method, 8, which is DEFLATE, and reserves 0 through 7.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> DEFLATE itself combines LZ77 and Huffman coding.</p>
<p>Once decompressed, the layout is simple. Every value is little-endian, and the whole file is a handful of integers followed by a long run of cells.<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup></p>
<table>
	<thead>
			<tr>
					<th>Field</th>
					<th>Type</th>
					<th>Size</th>
					<th>Notes</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>version</code></td>
					<td><code>i32</code></td>
					<td>4 bytes</td>
					<td>Always negative, usually <code>-1</code></td>
			</tr>
			<tr>
					<td>layer count</td>
					<td><code>i32</code></td>
					<td>4 bytes</td>
					<td>1 to 9 in REXPaint v1.70</td>
			</tr>
			<tr>
					<td><code>width</code></td>
					<td><code>i32</code></td>
					<td>4 bytes</td>
					<td>Once per layer</td>
			</tr>
			<tr>
					<td><code>height</code></td>
					<td><code>i32</code></td>
					<td>4 bytes</td>
					<td>Once per layer</td>
			</tr>
			<tr>
					<td><code>character</code></td>
					<td><code>i32</code></td>
					<td>4 bytes</td>
					<td>Once per cell</td>
			</tr>
			<tr>
					<td><code>foreground</code></td>
					<td>3 × <code>u8</code></td>
					<td>3 bytes</td>
					<td>RGB, once per cell</td>
			</tr>
			<tr>
					<td><code>background</code></td>
					<td>3 × <code>u8</code></td>
					<td>3 bytes</td>
					<td>RGB, once per cell</td>
			</tr>
	</tbody>
</table>
<p>To see those fields in a real file, we have to decompress it first. Piping the <a href="/test.xp">test file</a> from later in this post through <code>zcat</code> gives us:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ zcat ./test.xp <span class="p">|</span> xxd <span class="p">|</span> head -1
</span></span><span class="line"><span class="cl">00000000: ffff ffff <span class="m">0100</span> <span class="m">0000</span> <span class="m">4600</span> <span class="m">0000</span> <span class="m">0700</span> <span class="m">0000</span>  ........F.......
</span></span></code></pre></div><p>Read four bytes at a time, that&rsquo;s a <code>version</code> of <code>-1</code>, one layer, a <code>width</code> of 70 (<code>0x46</code>), and a <code>height</code> of 7.</p>
<p>Each layer stores its own <code>width</code> and <code>height</code>, followed by <code>width * height</code> cells of exactly 10 bytes each. The cells are in column-major order, meaning REXPaint writes the entire first column top to bottom, then the second column, and so on. Each cell&rsquo;s <code>character</code> field is an index into REXPaint&rsquo;s font, which uses the CP437 code page by default.<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> That means <code>1</code> is a smiley face (☺), not a control character.</p>
<p>Empty cells follow one more rule. REXPaint treats a background color of <code>255, 0, 255</code> (hot pink) as transparent, and every empty cell uses it, even on the base layer.<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> When layers are drawn on top of each other, any transparent cell still showing on the lowest layer should be drawn as black.</p>
<p>In Odin, each of those pieces becomes a <code>struct</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-odin" data-lang="odin"><span class="line"><span class="cl"><span class="n">Color</span><span class="w"> </span><span class="o">::</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">r</span><span class="o">:</span><span class="w"> </span><span class="kt">u8</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">g</span><span class="o">:</span><span class="w"> </span><span class="kt">u8</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">b</span><span class="o">:</span><span class="w"> </span><span class="kt">u8</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">Cell</span><span class="w"> </span><span class="o">::</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">character</span><span class="o">:</span><span class="w">  </span><span class="kt">i32</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">foreground</span><span class="o">:</span><span class="w"> </span><span class="n">Color</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">background</span><span class="o">:</span><span class="w"> </span><span class="n">Color</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">Layer</span><span class="w"> </span><span class="o">::</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">width</span><span class="o">:</span><span class="w">  </span><span class="kt">i32</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">height</span><span class="o">:</span><span class="w"> </span><span class="kt">i32</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">cells</span><span class="o">:</span><span class="w">  </span><span class="p">[]</span><span class="n">Cell</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">File</span><span class="w"> </span><span class="o">::</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">version</span><span class="o">:</span><span class="w"> </span><span class="kt">i32</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">layers</span><span class="o">:</span><span class="w">  </span><span class="p">[]</span><span class="n">Layer</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Decoding can fail in a few different ways, and we want to know which one happened. A plain <code>bool</code> can only tell us <em>that</em> something went wrong, not <em>why</em>, so we&rsquo;ll define our own error types up front:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-odin" data-lang="odin"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="s">&#34;core:compress/gzip&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">Read_Error</span><span class="w"> </span><span class="o">::</span><span class="w"> </span><span class="kd">enum</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">None</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">0</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">Offset_Goes_Past_Data</span><span class="p">,</span><span class="w"> </span><span class="c1">// The data ended before we finished reading
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">Unable_To_Read_Value</span><span class="p">,</span><span class="w"> </span><span class="c1">// A value couldn&#39;t be decoded
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">Bad_Dimensions</span><span class="p">,</span><span class="w"> </span><span class="c1">// Negative layer count, width, or height
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">Size_Overflow</span><span class="p">,</span><span class="w"> </span><span class="c1">// A layer has more cells than MAX_CELLS_PER_LAYER
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">Decode_Error</span><span class="w"> </span><span class="o">::</span><span class="w"> </span><span class="kd">union</span><span class="w"> </span><span class="nd">#shared_nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">gzip</span><span class="p">.</span><span class="n">Error</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">Read_Error</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><code>Read_Error</code> covers everything our own parsing can get wrong. <code>Decode_Error</code> wraps it together with gzip&rsquo;s error type, letting a single return value describe either kind of failure. The <code>#shared_nil</code> directive means a <code>nil</code> from any variant (including <code>Read_Error.None</code>) makes the whole union <code>nil</code>, and <code>err == nil</code> reads as &ldquo;everything worked.&rdquo; The procs that parse bytes will return a <code>Read_Error</code>, and only the top-level procs return the full <code>Decode_Error</code>.</p>
<h2 id="step-1-inflating-with-corecompressgzip">Step 1: Inflating with <code>core:compress/gzip</code></h2>
<p>Odin&rsquo;s <code>core:compress/gzip</code><sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup> package can decompress a file into a byte buffer for us. We&rsquo;ll create a procedure (Odin&rsquo;s name for a function, or &ldquo;proc&rdquo; for short) called <code>decode_from_file</code>, and stub out <code>decode_from_bytes</code> for later:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-odin" data-lang="odin"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="n">xp</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="s">&#34;core:bytes&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="s">&#34;core:compress/gzip&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">decode_from_file</span><span class="w"> </span><span class="o">::</span><span class="w"> </span><span class="kd">proc</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">path</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">allocator</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">context</span><span class="p">.</span><span class="n">allocator</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">f</span><span class="o">:</span><span class="w"> </span><span class="n">File</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">err</span><span class="o">:</span><span class="w"> </span><span class="n">Decode_Error</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">buf</span><span class="o">:</span><span class="w"> </span><span class="n">bytes</span><span class="p">.</span><span class="n">Buffer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="n">bytes</span><span class="p">.</span><span class="n">buffer_destroy</span><span class="p">(</span><span class="o">&amp;</span><span class="n">buf</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">gzip</span><span class="p">.</span><span class="n">load_from_file</span><span class="p">(</span><span class="n">path</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="n">allocator</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">allocator</span><span class="p">)</span><span class="w"> </span><span class="k">or_return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">raw</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="n">bytes</span><span class="p">.</span><span class="n">buffer_to_bytes</span><span class="p">(</span><span class="o">&amp;</span><span class="n">buf</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="n">decode_from_bytes</span><span class="p">(</span><span class="n">raw</span><span class="p">,</span><span class="w"> </span><span class="n">allocator</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">decode_from_bytes</span><span class="w"> </span><span class="o">::</span><span class="w"> </span><span class="kd">proc</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">b</span><span class="o">:</span><span class="w"> </span><span class="p">[]</span><span class="kt">u8</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">allocator</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">context</span><span class="p">.</span><span class="n">allocator</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">f</span><span class="o">:</span><span class="w"> </span><span class="n">File</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">err</span><span class="o">:</span><span class="w"> </span><span class="n">Decode_Error</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// TODO: read version and layers...
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="p">{},</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><code>decode_from_file</code> does the following:</p>
<ol>
<li>Declares an empty <code>bytes.Buffer</code><sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup> struct called <code>buf</code></li>
<li>Defers cleanup of <code>buf</code> until the end of the scope</li>
<li>Loads and decompresses the file at <code>path</code> into <code>buf</code> using <code>allocator</code></li>
<li>Gets the decompressed bytes out of <code>buf</code> with <code>bytes.buffer_to_bytes</code></li>
<li>Passes those bytes to the stubbed <code>decode_from_bytes</code> and returns its result</li>
</ol>
<p>The gzip line uses <a href="https://odin-lang.org/docs/overview/#or_return-operator"><code>or_return</code></a>, which is Odin&rsquo;s shorthand for the most common error-handling pattern. If the last value a call returns is a non-<code>nil</code> error, <code>or_return</code> returns that error from the current proc right away. Because <code>gzip.Error</code> is one of the variants of <code>Decode_Error</code>, gzip&rsquo;s error passes straight through to our caller. Without <code>or_return</code>, that line would look like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-odin" data-lang="odin"><span class="line"><span class="cl"><span class="c1">// Roughly the same as: gzip.load_from_file(path, &amp;buf, allocator = allocator) or_return
</span></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="w"> </span><span class="n">load_err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="n">gzip</span><span class="p">.</span><span class="n">load_from_file</span><span class="p">(</span><span class="n">path</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">buf</span><span class="p">,</span><span class="w"> </span><span class="n">allocator</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">allocator</span><span class="p">);</span><span class="w"> </span><span class="n">load_err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="p">{},</span><span class="w"> </span><span class="n">load_err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>You might notice that <code>raw</code> points into <code>buf</code>, which the <code>defer</code> frees. Freeing it is safe here, because the return expression runs before any deferred statement, and <code>decode_from_bytes</code> copies every value it needs into the <code>File</code>. We&rsquo;ll fill in that stub in just a bit!</p>
<p>At this point, we have the raw bytes of the decompressed <code>.xp</code> file. Turning them into a <code>File</code> struct means reading fixed-size chunks: 4 bytes for an <code>i32</code>, 1 byte for a <code>u8</code>.</p>
<h2 id="step-2-a-tiny-cursor">Step 2: A Tiny Cursor</h2>
<p>A parser only needs to know two things, the bytes and where it is in them, and a two-field <code>struct</code> covers both.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-odin" data-lang="odin"><span class="line"><span class="cl"><span class="n">Reader</span><span class="w"> </span><span class="o">::</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">raw</span><span class="o">:</span><span class="w">    </span><span class="p">[]</span><span class="kt">u8</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">offset</span><span class="o">:</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>On top of <code>Reader</code>, we need two helpers: one that reads an <code>i32</code> and one that reads a <code>u8</code>. Each one returns the value and moves <code>offset</code> forward.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-odin" data-lang="odin"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="s">&#34;core:encoding/endian&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">read_i32</span><span class="w"> </span><span class="o">::</span><span class="w"> </span><span class="kd">proc</span><span class="p">(</span><span class="n">r</span><span class="o">:</span><span class="w"> </span><span class="o">^</span><span class="n">Reader</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="p">(</span><span class="n">v</span><span class="o">:</span><span class="w"> </span><span class="kt">i32</span><span class="p">,</span><span class="w"> </span><span class="n">err</span><span class="o">:</span><span class="w"> </span><span class="n">Read_Error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="n">r</span><span class="p">.</span><span class="n">offset</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">4</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="k">len</span><span class="p">(</span><span class="n">r</span><span class="p">.</span><span class="n">raw</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">0</span><span class="p">,</span><span class="w"> </span><span class="p">.</span><span class="n">Offset_Goes_Past_Data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">val</span><span class="p">,</span><span class="w"> </span><span class="n">ok</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="n">endian</span><span class="p">.</span><span class="n">get_i32</span><span class="p">(</span><span class="n">r</span><span class="p">.</span><span class="n">raw</span><span class="p">[</span><span class="n">r</span><span class="p">.</span><span class="n">offset</span><span class="o">:</span><span class="p">],</span><span class="w"> </span><span class="p">.</span><span class="n">Little</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="err">!</span><span class="n">ok</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">0</span><span class="p">,</span><span class="w"> </span><span class="p">.</span><span class="n">Unable_To_Read_Value</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">r</span><span class="p">.</span><span class="n">offset</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="nx">4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="n">val</span><span class="p">,</span><span class="w"> </span><span class="p">.</span><span class="n">None</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">read_u8</span><span class="w"> </span><span class="o">::</span><span class="w"> </span><span class="kd">proc</span><span class="p">(</span><span class="n">r</span><span class="o">:</span><span class="w"> </span><span class="o">^</span><span class="n">Reader</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="p">(</span><span class="n">v</span><span class="o">:</span><span class="w"> </span><span class="kt">u8</span><span class="p">,</span><span class="w"> </span><span class="n">err</span><span class="o">:</span><span class="w"> </span><span class="n">Read_Error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="n">r</span><span class="p">.</span><span class="n">offset</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="k">len</span><span class="p">(</span><span class="n">r</span><span class="p">.</span><span class="n">raw</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">0</span><span class="p">,</span><span class="w"> </span><span class="p">.</span><span class="n">Offset_Goes_Past_Data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">val</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="n">r</span><span class="p">.</span><span class="n">raw</span><span class="p">[</span><span class="n">r</span><span class="p">.</span><span class="n">offset</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">r</span><span class="p">.</span><span class="n">offset</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="nx">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="n">val</span><span class="p">,</span><span class="w"> </span><span class="p">.</span><span class="n">None</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><code>read_i32</code> leans on the <code>core:encoding/endian</code> package<sup id="fnref:9"><a href="#fn:9" class="footnote-ref" role="doc-noteref">9</a></sup> to decode 4 little-endian bytes. <code>endian.get_i32</code> does its own length check and returns <code>false</code> when fewer than 4 bytes are left, but it can&rsquo;t tell us <em>why</em> it failed. Checking the length ourselves first lets us report <code>.Offset_Goes_Past_Data</code>, the error a truncated file will actually hit. <code>read_u8</code> indexes the slice directly, and without its check, reading past the end would panic.</p>
<h2 id="step-3-parsing-layers-and-cells">Step 3: Parsing Layers and Cells</h2>
<p>The first field we need to parse is <code>version</code>. Our new <code>read_i32</code> proc handles that, and then we hand the rest of the bytes off to <code>read_layers</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-odin" data-lang="odin"><span class="line"><span class="cl"><span class="n">decode_from_bytes</span><span class="w"> </span><span class="o">::</span><span class="w"> </span><span class="kd">proc</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">b</span><span class="o">:</span><span class="w"> </span><span class="p">[]</span><span class="kt">u8</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">allocator</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">context</span><span class="p">.</span><span class="n">allocator</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">f</span><span class="o">:</span><span class="w"> </span><span class="n">File</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">err</span><span class="o">:</span><span class="w"> </span><span class="n">Decode_Error</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">r</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="n">Reader</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="n">raw</span><span class="w">    </span><span class="o">=</span><span class="w"> </span><span class="n">b</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="n">offset</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">0</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">f</span><span class="p">.</span><span class="n">version</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">read_i32</span><span class="p">(</span><span class="o">&amp;</span><span class="n">r</span><span class="p">)</span><span class="w"> </span><span class="k">or_return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">f</span><span class="p">.</span><span class="n">layers</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">read_layers</span><span class="p">(</span><span class="o">&amp;</span><span class="n">r</span><span class="p">,</span><span class="w"> </span><span class="n">allocator</span><span class="p">)</span><span class="w"> </span><span class="k">or_return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="n">f</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><code>read_i32</code> returns a <code>Read_Error</code>, but <code>decode_from_bytes</code> returns a <code>Decode_Error</code>. <code>or_return</code> still works here because <code>Read_Error</code> is one of the union&rsquo;s variants.</p>
<p><code>read_layers</code> is where things get more interesting:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-odin" data-lang="odin"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="s">&#34;base:runtime&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// A layer header consists of a width and height. i32 + i32 = 4 bytes + 4 bytes = 8 bytes
</span></span></span><span class="line"><span class="cl"><span class="n">LAYER_HEADER_BYTES</span><span class="w"> </span><span class="o">::</span><span class="w"> </span><span class="nx">8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">read_layers</span><span class="w"> </span><span class="o">::</span><span class="w"> </span><span class="kd">proc</span><span class="p">(</span><span class="n">r</span><span class="o">:</span><span class="w"> </span><span class="o">^</span><span class="n">Reader</span><span class="p">,</span><span class="w"> </span><span class="n">allocator</span><span class="o">:</span><span class="w"> </span><span class="n">runtime</span><span class="p">.</span><span class="n">Allocator</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="p">(</span><span class="n">l</span><span class="o">:</span><span class="w"> </span><span class="p">[]</span><span class="n">Layer</span><span class="p">,</span><span class="w"> </span><span class="n">err</span><span class="o">:</span><span class="w"> </span><span class="n">Read_Error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">count</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="n">read_i32</span><span class="p">(</span><span class="n">r</span><span class="p">)</span><span class="w"> </span><span class="k">or_return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="n">count</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="nx">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="p">,</span><span class="w"> </span><span class="p">.</span><span class="n">Bad_Dimensions</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Every layer needs at least its header, so a count the remaining bytes
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// can&#39;t hold is bogus. Reject it before allocating.
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="kt">i64</span><span class="p">(</span><span class="n">count</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">LAYER_HEADER_BYTES</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="kt">i64</span><span class="p">(</span><span class="k">len</span><span class="p">(</span><span class="n">r</span><span class="p">.</span><span class="n">raw</span><span class="p">)</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">r</span><span class="p">.</span><span class="n">offset</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="p">,</span><span class="w"> </span><span class="p">.</span><span class="n">Offset_Goes_Past_Data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">l</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">make</span><span class="p">([]</span><span class="n">Layer</span><span class="p">,</span><span class="w"> </span><span class="kt">int</span><span class="p">(</span><span class="n">count</span><span class="p">),</span><span class="w"> </span><span class="n">allocator</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">allocator</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Clean up our mess if there was an error.
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="p">.</span><span class="n">None</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">for</span><span class="w"> </span><span class="n">layer</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">l</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">delete</span><span class="p">(</span><span class="n">layer</span><span class="p">.</span><span class="n">cells</span><span class="p">,</span><span class="w"> </span><span class="n">allocator</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">delete</span><span class="p">(</span><span class="n">l</span><span class="p">,</span><span class="w"> </span><span class="n">allocator</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="nx">0</span><span class="w"> </span><span class="o">..&lt;</span><span class="w"> </span><span class="kt">int</span><span class="p">(</span><span class="n">count</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="n">l</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">read_layer</span><span class="p">(</span><span class="n">r</span><span class="p">,</span><span class="w"> </span><span class="n">allocator</span><span class="p">)</span><span class="w"> </span><span class="k">or_return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="n">l</span><span class="p">,</span><span class="w"> </span><span class="p">.</span><span class="n">None</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>There are two ideas here worth slowing down for.</p>
<p>The first is that we never trust a number from the file until we&rsquo;ve checked it against the data we actually have. The layer count is just 4 bytes that someone else wrote. If a file is truncated or corrupted, it could claim to have two billion layers, and a naive <code>make</code> would try to allocate all of them. Each layer needs at least an 8-byte header. Any count that couldn&rsquo;t possibly fit in the remaining bytes gets rejected before we allocate anything. We also widen to <code>i64</code> before multiplying, because a large <code>count * 8</code> can overflow an <code>i32</code>.</p>
<p>The second is <a href="https://odin-lang.org/docs/overview/#defer-if"><code>defer if</code></a>. Odin&rsquo;s <code>defer</code> runs a statement when the scope exits, and <code>defer if</code> only runs it if the condition is true at that moment. Here the condition is <code>err != .None</code>, so the block only runs on failure. If any <code>read_layer</code> call fails partway through, <code>or_return</code> sets <code>err</code> to that error and returns, and the deferred block frees every layer we already read. On success, <code>err</code> is <code>.None</code> and the caller owns the memory.</p>
<p><code>read_layer</code> follows the same pattern:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-odin" data-lang="odin"><span class="line"><span class="cl"><span class="c1">// A sanity limit, so a corrupt header can&#39;t ask for a huge layer.
</span></span></span><span class="line"><span class="cl"><span class="n">MAX_CELLS_PER_LAYER</span><span class="w"> </span><span class="o">::</span><span class="w"> </span><span class="nx">4096</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">4096</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// A cell consists of a character, a foreground color, and a background color.
</span></span></span><span class="line"><span class="cl"><span class="c1">// i32 + 3 × u8 + 3 × u8 = 4 bytes + 3 bytes + 3 bytes = 10 bytes
</span></span></span><span class="line"><span class="cl"><span class="n">CELL_BYTES</span><span class="w"> </span><span class="o">::</span><span class="w"> </span><span class="nx">10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">read_layer</span><span class="w"> </span><span class="o">::</span><span class="w"> </span><span class="kd">proc</span><span class="p">(</span><span class="n">r</span><span class="o">:</span><span class="w"> </span><span class="o">^</span><span class="n">Reader</span><span class="p">,</span><span class="w"> </span><span class="n">allocator</span><span class="o">:</span><span class="w"> </span><span class="n">runtime</span><span class="p">.</span><span class="n">Allocator</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="p">(</span><span class="n">l</span><span class="o">:</span><span class="w"> </span><span class="n">Layer</span><span class="p">,</span><span class="w"> </span><span class="n">err</span><span class="o">:</span><span class="w"> </span><span class="n">Read_Error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">width</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="n">read_i32</span><span class="p">(</span><span class="n">r</span><span class="p">)</span><span class="w"> </span><span class="k">or_return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">height</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="n">read_i32</span><span class="p">(</span><span class="n">r</span><span class="p">)</span><span class="w"> </span><span class="k">or_return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="nx">0</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="n">height</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="nx">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="p">{},</span><span class="w"> </span><span class="p">.</span><span class="n">Bad_Dimensions</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">l</span><span class="p">.</span><span class="n">width</span><span class="p">,</span><span class="w"> </span><span class="n">l</span><span class="p">.</span><span class="n">height</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">width</span><span class="p">,</span><span class="w"> </span><span class="n">height</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">n</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="kt">i64</span><span class="p">(</span><span class="n">l</span><span class="p">.</span><span class="n">width</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="kt">i64</span><span class="p">(</span><span class="n">l</span><span class="p">.</span><span class="n">height</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="n">MAX_CELLS_PER_LAYER</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="p">{},</span><span class="w"> </span><span class="p">.</span><span class="n">Size_Overflow</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Same idea as the layer count: don&#39;t allocate for cells that aren&#39;t there.
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">CELL_BYTES</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="kt">i64</span><span class="p">(</span><span class="k">len</span><span class="p">(</span><span class="n">r</span><span class="p">.</span><span class="n">raw</span><span class="p">)</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">r</span><span class="p">.</span><span class="n">offset</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="p">{},</span><span class="w"> </span><span class="p">.</span><span class="n">Offset_Goes_Past_Data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">l</span><span class="p">.</span><span class="n">cells</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">make</span><span class="p">([]</span><span class="n">Cell</span><span class="p">,</span><span class="w"> </span><span class="kt">int</span><span class="p">(</span><span class="n">n</span><span class="p">),</span><span class="w"> </span><span class="n">allocator</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">allocator</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="p">.</span><span class="n">None</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">delete</span><span class="p">(</span><span class="n">l</span><span class="p">.</span><span class="n">cells</span><span class="p">,</span><span class="w"> </span><span class="n">allocator</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="nx">0</span><span class="w"> </span><span class="o">..&lt;</span><span class="w"> </span><span class="kt">int</span><span class="p">(</span><span class="n">n</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="n">l</span><span class="p">.</span><span class="n">cells</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">read_cell</span><span class="p">(</span><span class="n">r</span><span class="p">)</span><span class="w"> </span><span class="k">or_return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="n">l</span><span class="p">,</span><span class="w"> </span><span class="p">.</span><span class="n">None</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Notice that a failed <code>read_layer</code> cleans up its own cells. Because <code>or_return</code> exits before <code>l[i]</code> is assigned in <code>read_layers</code>, the failed layer never makes it into the slice. Nothing gets freed twice.</p>
<p>Finally, cells and colors are just a few reads in a row:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-odin" data-lang="odin"><span class="line"><span class="cl"><span class="n">read_cell</span><span class="w"> </span><span class="o">::</span><span class="w"> </span><span class="kd">proc</span><span class="p">(</span><span class="n">r</span><span class="o">:</span><span class="w"> </span><span class="o">^</span><span class="n">Reader</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="p">(</span><span class="n">c</span><span class="o">:</span><span class="w"> </span><span class="n">Cell</span><span class="p">,</span><span class="w"> </span><span class="n">err</span><span class="o">:</span><span class="w"> </span><span class="n">Read_Error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">c</span><span class="p">.</span><span class="n">character</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">read_i32</span><span class="p">(</span><span class="n">r</span><span class="p">)</span><span class="w"> </span><span class="k">or_return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">c</span><span class="p">.</span><span class="n">foreground</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">read_color</span><span class="p">(</span><span class="n">r</span><span class="p">)</span><span class="w"> </span><span class="k">or_return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">c</span><span class="p">.</span><span class="n">background</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">read_color</span><span class="p">(</span><span class="n">r</span><span class="p">)</span><span class="w"> </span><span class="k">or_return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="n">c</span><span class="p">,</span><span class="w"> </span><span class="p">.</span><span class="n">None</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">read_color</span><span class="w"> </span><span class="o">::</span><span class="w"> </span><span class="kd">proc</span><span class="p">(</span><span class="n">r</span><span class="o">:</span><span class="w"> </span><span class="o">^</span><span class="n">Reader</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="p">(</span><span class="n">c</span><span class="o">:</span><span class="w"> </span><span class="n">Color</span><span class="p">,</span><span class="w"> </span><span class="n">err</span><span class="o">:</span><span class="w"> </span><span class="n">Read_Error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">c</span><span class="p">.</span><span class="n">r</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">read_u8</span><span class="p">(</span><span class="n">r</span><span class="p">)</span><span class="w"> </span><span class="k">or_return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">c</span><span class="p">.</span><span class="n">g</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">read_u8</span><span class="p">(</span><span class="n">r</span><span class="p">)</span><span class="w"> </span><span class="k">or_return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">c</span><span class="p">.</span><span class="n">b</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">read_u8</span><span class="p">(</span><span class="n">r</span><span class="p">)</span><span class="w"> </span><span class="k">or_return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="n">c</span><span class="p">,</span><span class="w"> </span><span class="p">.</span><span class="n">None</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Remember that cells are stored in column-major order. The cell at <code>(x, y)</code> lives at index <code>x * height + y</code>, not <code>y * width + x</code>. A small helper keeps that detail in one place:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-odin" data-lang="odin"><span class="line"><span class="cl"><span class="n">cell_at</span><span class="w"> </span><span class="o">::</span><span class="w"> </span><span class="kd">proc</span><span class="p">(</span><span class="n">l</span><span class="o">:</span><span class="w"> </span><span class="n">Layer</span><span class="p">,</span><span class="w"> </span><span class="n">x</span><span class="p">,</span><span class="w"> </span><span class="n">y</span><span class="o">:</span><span class="w"> </span><span class="kt">i32</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="n">Cell</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="n">l</span><span class="p">.</span><span class="n">cells</span><span class="p">[</span><span class="n">x</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">l</span><span class="p">.</span><span class="n">height</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">y</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h2 id="step-4-putting-it-to-use">Step 4: Putting It to Use</h2>
<p><code>decode_from_bytes</code> hands back a <code>File</code> with nested allocations, since every layer in its slice owns a slice of cells. Whoever calls it owns all of that memory and needs a way to free it with the same allocator that decoded it:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-odin" data-lang="odin"><span class="line"><span class="cl"><span class="n">file_destroy</span><span class="w"> </span><span class="o">::</span><span class="w"> </span><span class="kd">proc</span><span class="p">(</span><span class="n">f</span><span class="o">:</span><span class="w"> </span><span class="n">File</span><span class="p">,</span><span class="w"> </span><span class="n">allocator</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">context</span><span class="p">.</span><span class="n">allocator</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="n">layer</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">f</span><span class="p">.</span><span class="n">layers</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">delete</span><span class="p">(</span><span class="n">layer</span><span class="p">.</span><span class="n">cells</span><span class="p">,</span><span class="w"> </span><span class="n">allocator</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">delete</span><span class="p">(</span><span class="n">f</span><span class="p">.</span><span class="n">layers</span><span class="p">,</span><span class="w"> </span><span class="n">allocator</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Now we can decode a real file and print its first layer to the terminal:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-odin" data-lang="odin"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="n">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="s">&#34;core:fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="s">&#34;xp&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">main</span><span class="w"> </span><span class="o">::</span><span class="w"> </span><span class="kd">proc</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">f</span><span class="p">,</span><span class="w"> </span><span class="n">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="n">xp</span><span class="p">.</span><span class="n">decode_from_file</span><span class="p">(</span><span class="s">&#34;test.xp&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="n">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="n">fmt</span><span class="p">.</span><span class="n">eprintln</span><span class="p">(</span><span class="s">&#34;failed to decode test.xp:&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="n">xp</span><span class="p">.</span><span class="n">file_destroy</span><span class="p">(</span><span class="n">f</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="k">len</span><span class="p">(</span><span class="n">f</span><span class="p">.</span><span class="n">layers</span><span class="p">)</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nx">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="n">fmt</span><span class="p">.</span><span class="n">eprintln</span><span class="p">(</span><span class="s">&#34;test.xp has no layers&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="n">layer</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="n">f</span><span class="p">.</span><span class="n">layers</span><span class="p">[</span><span class="nx">0</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="nx">0</span><span class="w"> </span><span class="o">..&lt;</span><span class="w"> </span><span class="n">layer</span><span class="p">.</span><span class="n">height</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">for</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="nx">0</span><span class="w"> </span><span class="o">..&lt;</span><span class="w"> </span><span class="n">layer</span><span class="p">.</span><span class="n">width</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="n">c</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="n">xp</span><span class="p">.</span><span class="n">cell_at</span><span class="p">(</span><span class="n">layer</span><span class="p">,</span><span class="w"> </span><span class="n">x</span><span class="p">,</span><span class="w"> </span><span class="n">y</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="c1">// CP437 and ASCII only agree on the printable range.
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">if</span><span class="w"> </span><span class="n">c</span><span class="p">.</span><span class="n">character</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="nx">32</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="n">c</span><span class="p">.</span><span class="n">character</span><span class="w"> </span><span class="o">&lt;=</span><span class="w"> </span><span class="nx">126</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="n">fmt</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="kt">rune</span><span class="p">(</span><span class="n">c</span><span class="p">.</span><span class="n">character</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="n">fmt</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="sc">&#39; &#39;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="n">fmt</span><span class="p">.</span><span class="n">println</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>If decoding fails, <code>fmt</code> prints the variant inside the union, so a truncated file reports <code>Offset_Goes_Past_Data</code> instead of a bare &ldquo;it didn&rsquo;t work.&rdquo; A caller that needs to react differently could <code>switch</code> on <code>err</code> instead. Running it against the <a href="/test.xp">small test file</a>, where each letter is drawn out of copies of itself, gives us:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl"> H   H EEEEE L     L      OOO         W   W  OOO  RRRR  L     DDDD  !
</span></span><span class="line"><span class="cl"> H   H E     L     L     O   O        W   W O   O R   R L     D   D !
</span></span><span class="line"><span class="cl"> HHHHH EEEE  L     L     O   O        W W W O   O RRRR  L     D   D !
</span></span><span class="line"><span class="cl"> H   H E     L     L     O   O  ,     WW WW O   O R  R  L     D   D
</span></span><span class="line"><span class="cl"> H   H EEEEE LLLLL LLLLL  OOO  ,      W   W  OOO  R   R LLLLL DDDD  !
</span></span></code></pre></div><p>Because the terminal prints row by row, we loop <code>y</code> on the outside and <code>x</code> on the inside. <code>cell_at</code> takes care of the column-major storage underneath. Glyphs outside the printable ASCII range need a CP437-to-Unicode lookup table to display correctly, but this is enough to see our screen show up.</p>
<h2 id="wrap-up">Wrap-Up</h2>
<p>In about 150 lines of Odin, we took a binary file, decompressed it, and mapped its bytes into a structure our program can use anywhere. Going the other way and <em>writing</em> <code>.xp</code> files from a <code>File</code> struct is mostly the same code in reverse, though we&rsquo;d need something outside Odin&rsquo;s standard library to handle the compression. If you want to see the whole thing in one place, the full source is on Codeberg: <a href="https://codeberg.org/telton/fimbul/src/branch/volund/src/xp/decode.odin"><code>decode.odin</code></a> and <a href="https://codeberg.org/telton/fimbul/src/branch/volund/src/xp/xp.odin"><code>xp.odin</code></a>.</p>
<p>In a future post, I&rsquo;d like to go over how I&rsquo;m using <code>.xp</code> files in Fimbul for title, win, and death screens, as well as prefabs that spawn enemies and items. In the meantime, try running a <code>.xp</code> file through <code>zcat</code> and <code>xxd</code> and see how much of it you can read by hand now!</p>
<p>Until next time 🤙</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://www.gridsagegames.com/rexpaint/manual.txt">REXPaint v1.70 manual (plaintext)</a>, Grid Sage Games&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://steveasleep.com/rexpaint_manual.html">REXPaint manual (HTML)</a>, hosted by Steve Landey&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://datatracker.ietf.org/doc/html/rfc1952#section-2.3.1">RFC 1952: GZIP file format specification, section 2.3.1</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>&ldquo;Appendix B: .xp Format Specification&rdquo; in the <a href="https://steveasleep.com/rexpaint_manual.html#appendix-b:-.xp-format-specification-(and-import-libraries)">HTML manual</a>, also available in the <a href="https://www.gridsagegames.com/rexpaint/manual.txt">official plaintext manual</a>&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p><a href="https://steveasleep.com/rexpaint_manual.html#fonts">REXPaint manual: Fonts</a>&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p><a href="https://steveasleep.com/rexpaint_manual.html#transparency">REXPaint manual: Transparency</a> and <a href="https://steveasleep.com/rexpaint_manual.html#appendix-b:-.xp-format-specification-(and-import-libraries)">Appendix B</a>&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p><a href="https://pkg.odin-lang.org/core/compress/gzip">Odin <code>core:compress/gzip</code> package</a>&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:8">
<p><a href="https://pkg.odin-lang.org/core/bytes/#Buffer">Odin <code>bytes.Buffer</code> struct</a>&#160;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:9">
<p><a href="https://pkg.odin-lang.org/core/encoding/endian/">Odin <code>core:encoding/endian</code> package</a>&#160;<a href="#fnref:9" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
