{"id":744,"date":"2023-03-26T12:50:46","date_gmt":"2023-03-26T19:50:46","guid":{"rendered":"https:\/\/porkrind.org\/missives\/?p=744"},"modified":"2023-03-26T18:22:07","modified_gmt":"2023-03-27T01:22:07","slug":"decoding-the-sprite-format-of-a-25-year-old-game","status":"publish","type":"post","link":"https:\/\/porkrind.org\/missives\/decoding-the-sprite-format-of-a-25-year-old-game\/","title":{"rendered":"Decoding the sprite format of a 25 year old game"},"content":{"rendered":"<p>My brother <a href=\"https:\/\/xkcd.com\/356\/\">nerd sniped<\/a> me the other day. He wanted to see if he could extract the images out of an old game by <a href=\"https:\/\/en.wikipedia.org\/wiki\/Ambrosia_Software\">Ambrosia Software<\/a> called <a href=\"https:\/\/web.archive.org\/web\/19990420141158\/http:\/\/ambrosiasw.com\/Products\/Slithereens.html\">&#8220;Slithereens&#8221;<\/a>. It was released on 1998-12-15 for Mac OS 9. That sounds easy enough\u2026<\/p>\n<p><a href=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/slither-crop.png\"><img loading=\"lazy\" decoding=\"async\" data-attachment-id=\"754\" data-permalink=\"https:\/\/porkrind.org\/missives\/decoding-the-sprite-format-of-a-25-year-old-game\/slither-crop\/\" data-orig-file=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/slither-crop.png\" data-orig-size=\"639,479\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"Slithereen Screenshot\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/slither-crop-300x225.png\" data-large-file=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/slither-crop.png\" src=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/slither-crop.png\" alt=\"\" width=\"639\" height=\"479\" class=\"aligncenter size-full wp-image-754\" srcset=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/slither-crop.png 639w, https:\/\/porkrind.org\/wp-uploads\/2023\/03\/slither-crop-300x225.png 300w\" sizes=\"auto, (max-width: 639px) 100vw, 639px\" \/><\/a><\/p>\n<h2>Digging though resource forks<\/h2>\n<p>Old Mac OS had the ability to have a 2nd plane of data on a file called a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Resource_fork\">&#8220;resource fork&#8221;<\/a>. Rather than being just arbitrary binary blobs, they had some structure to them. Back in the day there was tool called <a href=\"https:\/\/en.wikipedia.org\/wiki\/ResEdit\">&#8220;ResEdit&#8221;<\/a> from Apple that let you poke around Resource Forks. Today there are open source clones out there. I grabbed <a href=\"https:\/\/github.com\/andrews05\/ResForge\">&#8220;ResForge&#8221;<\/a> to look around with a nice GUI. I could have also used <code>\/usr\/bin\/derez<\/code> which still ships with the developer tools even on the latest macOS 13.<\/p>\n<p>Here&#8217;s the list of resources in the Slithereen Sprites resource files:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" data-attachment-id=\"767\" data-permalink=\"https:\/\/porkrind.org\/missives\/decoding-the-sprite-format-of-a-25-year-old-game\/screenshot-2023-03-21-at-11-28-49-pm\/\" data-orig-file=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-11.28.49-PM.png\" data-orig-size=\"104,146\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"ResForge Resource Types\" data-image-description=\"\" data-image-caption=\"&lt;p&gt;Resource Types&lt;\/p&gt;\n\" data-medium-file=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-11.28.49-PM.png\" data-large-file=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-11.28.49-PM.png\" src=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-11.28.49-PM.png\" alt=\"\" width=\"104\" height=\"146\" class=\"aligncenter size-full wp-image-767\" \/><\/p>\n<p>There&#8217;s just a handful of types. Poking around it looks like the <code>SpRt<\/code> resources contain metadata about the animations and the <code>SpRd<\/code> resources are just raw data, probably bitmaps.<\/p>\n<p>Here&#8217;s a <code>SpRt<\/code> resource for the &#8220;Player 1 Head&#8221; sprite:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" data-attachment-id=\"753\" data-permalink=\"https:\/\/porkrind.org\/missives\/decoding-the-sprite-format-of-a-25-year-old-game\/screenshot-2023-03-21-at-2-05-53-pm\/\" data-orig-file=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-2.05.53-PM.png\" data-orig-size=\"652,499\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"ResForge SpRt\" data-image-description=\"\" data-image-caption=\"&lt;p&gt;&#8216;Player 1 Head&#8217;&lt;\/p&gt;\n\" data-medium-file=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-2.05.53-PM-300x230.png\" data-large-file=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-2.05.53-PM.png\" src=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-2.05.53-PM.png\" alt=\"\" width=\"652\" height=\"499\" class=\"aligncenter size-full wp-image-753\" srcset=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-2.05.53-PM.png 652w, https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-2.05.53-PM-300x230.png 300w\" sizes=\"auto, (max-width: 652px) 100vw, 652px\" \/><\/p>\n<p>That&#8217;s got a bunch of stuff, but really the only super interesting piece of data is the width: 24 pixels.<\/p>\n<p>As an aside, how did they get their custom animation data to show up parsed like that? That&#8217;s actually a cool feature of ResEdit\u2014you can defined <code>TMPL<\/code> resources in your file and ResEdit will use them to decode and print your data nicely, allowing you to edit stuff right right from ResEdit and without any custom tools. Many programs would ship with their <code>TMPL<\/code>s and so you could open the application with ResEdit and poke around their data. It&#8217;s sort of the equivalent of finding a <code>.json<\/code> file in someones app nowadays. Here&#8217;s the <code>TMPL<\/code> for the <code>SpRt<\/code> resource:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" data-attachment-id=\"751\" data-permalink=\"https:\/\/porkrind.org\/missives\/decoding-the-sprite-format-of-a-25-year-old-game\/screenshot-2023-03-21-at-2-06-19-pm\/\" data-orig-file=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-2.06.19-PM.png\" data-orig-size=\"652,499\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"TMPL Resource\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-2.06.19-PM-300x230.png\" data-large-file=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-2.06.19-PM.png\" src=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-2.06.19-PM.png\" alt=\"\" width=\"652\" height=\"499\" class=\"aligncenter size-full wp-image-751\" srcset=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-2.06.19-PM.png 652w, https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-2.06.19-PM-300x230.png 300w\" sizes=\"auto, (max-width: 652px) 100vw, 652px\" \/><\/p>\n<p>I didn&#8217;t want to start with the &#8220;Player 1 Head&#8221; sprite though. I wanted something more recognizable that would help when looking for patterns. A 24&#215;24 square with a semi-circular snake head in the middle seemed to hard. I checked out the screenshot and noticed the &#8220;Level&#8221; text in the bottom right corner, which seemed promising:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" data-attachment-id=\"782\" data-permalink=\"https:\/\/porkrind.org\/missives\/decoding-the-sprite-format-of-a-25-year-old-game\/screenshot-2023-03-22-at-11-05-35-am\/\" data-orig-file=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-22-at-11.05.35-AM.png\" data-orig-size=\"232,53\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"Level Zoomed\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-22-at-11.05.35-AM.png\" data-large-file=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-22-at-11.05.35-AM.png\" src=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-22-at-11.05.35-AM.png\" alt=\"\" width=\"464\" height=\"106\" class=\"aligncenter size-full wp-image-782\" id=\"level-image\" \/><\/p>\n<p>Distinctive shape, wider than it is tall. It looked like it was in a sprite called &#8220;Level&#8221;, so I checked out the <code>SpRt<\/code> resource:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" data-attachment-id=\"752\" data-permalink=\"https:\/\/porkrind.org\/missives\/decoding-the-sprite-format-of-a-25-year-old-game\/screenshot-2023-03-21-at-2-05-59-pm\/\" data-orig-file=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-2.05.59-PM.png\" data-orig-size=\"608,371\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"&#8220;Level&#8221; SpRt Resource\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-2.05.59-PM-300x183.png\" data-large-file=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-2.05.59-PM.png\" src=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-2.05.59-PM.png\" alt=\"\" width=\"608\" height=\"371\" class=\"aligncenter size-full wp-image-752\" srcset=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-2.05.59-PM.png 608w, https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-2.05.59-PM-300x183.png 300w\" sizes=\"auto, (max-width: 608px) 100vw, 608px\" \/><\/p>\n<p>It&#8217;s just a single animation frame, which seems like a good idea to start with\u2014less complications up front.<\/p>\n<p>Width 85. I can remember that number&#8230; But no Height for some reason. I zoomed up the screenshot and manually counted the pixels in the Level sprite. It looks like it is 22 pixels high.<\/p>\n<p>Now lets look at the data (in the <code>SpRd<\/code> resource):<\/p>\n<p><a href=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-2.07.04-PM.png\"><img loading=\"lazy\" decoding=\"async\" data-attachment-id=\"750\" data-permalink=\"https:\/\/porkrind.org\/missives\/decoding-the-sprite-format-of-a-25-year-old-game\/screenshot-2023-03-21-at-2-07-04-pm\/\" data-orig-file=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-2.07.04-PM.png\" data-orig-size=\"980,961\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"&#8220;Level&#8221; SpRd Resource\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-2.07.04-PM-300x294.png\" data-large-file=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-2.07.04-PM.png\" src=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-2.07.04-PM.png\" alt=\"\" width=\"980\" height=\"961\" class=\"aligncenter size-full wp-image-750\" srcset=\"https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-2.07.04-PM.png 980w, https:\/\/porkrind.org\/wp-uploads\/2023\/03\/Screenshot-2023-03-21-at-2.07.04-PM-300x294.png 300w\" sizes=\"auto, (max-width: 980px) 100vw, 980px\" \/><\/a><\/p>\n<p>I glance at this a little, but my eyes glaze over. It&#8217;s probably just bitmap data, after all.<\/p>\n<p>What I don&#8217;t know, though, is the layout of the bitmap data. It <em>probably<\/em> goes top left to bottom right (as was the style of the time), but that&#8217;s not a given. What if it&#8217;s rotated or something weird? And how many Bits per Pixel (&#8220;BPP&#8221;) is it? 8 bit seems reasonable for that timeframe, but it could also be 4\u2014it doesn&#8217;t look like the sprites each have a ton of color.<\/p>\n<p>So I&#8217;m thinking I want a little data structure that I can read arbitrary bits from so that I can try rendering the bitmap with different BPPs. I decided to write it in Ruby and came up with this little class:<\/p>\n<pre><code class=\"language-ruby\">class Bits\n  def initialize(bytes=[])\n    @bytes = bytes\n  end\n\n  def get(offset, length)\n    v=0\n    while length &gt; 0\n      start = offset \/ 8\n      bit   = offset % 8\n\n      bs = bit\n      be = [8, bs+length].min\n      bits = be - bit\n      v = v&lt;&lt;bits | @bytes[start] &gt;&gt; (8 - be) &amp; 0xff &gt;&gt; (8 - bits)\n\n      offset+=bits\n      length-=bits\n    end\n    v\n  end\nend\n<\/code><\/pre>\n<p>I also need the sprite data. I&#8217;ve saved the RsRd file out from ResForge and I can poke an prod it from the command line now. I read the man page for <code>xxd<\/code> and discovered the <code>-i<\/code> option which sounds perfect:<\/p>\n<blockquote><p>\n  -i | -include:  Output in C include file style. A complete static array definition is written (named after the input file), unless xxd reads from stdin.\n<\/p><\/blockquote>\n<p>Neat. Now I can just do:<\/p>\n<pre><code class=\"language-shell-session\">$ xxd -i Level.sprd &gt; level.rb\n<\/code><\/pre>\n<p>And then convert the C-ism to Ruby-ism in Emacs.<\/p>\n<p>I also write a little dump routine to dump the bitmap in rows and columns:<\/p>\n<pre><code class=\"language-ruby\">def dump(data, width, height, bpp, start=0, stride=width)\n  (0..height).each {|y|\n    (0..width).each {|x|\n      pix=data.get(start + y * stride * bpp + x * bpp, bpp)\n      printf(\"%*b \", bpp, pix)\n    }\n    puts \"\"\n  }\nend\n<\/code><\/pre>\n<p>I don&#8217;t know if I&#8217;ll need stride or start, but it&#8217;s easy enough to add them now\u2014maybe they&#8217;ll turn out to be important later.<\/p>\n<pre><code class=\"language-ruby\">dump(head, 12, 12, 4)\n<\/code><\/pre>\n<p>produces<br \/>\n<small><\/p>\n<pre><code>   0    0    0    0    0    0    0    0    0    0    1 1000    0\n   0    0    1 1000    0    0    0    0    0   10  101    0    0\n   0    0    0    0    0    1    1   11    0    0    1 1000    0\n   0    1   10 1011    0    1    0    0    0    0    0    0    0\n   0    1    0    0    0    0    0    0    0    1    0    0    0\n   0    0    0    0    0    1    0    0    0    0    0    0    0\n   0    1    0    0    0    0    1  100    0   11    0    0    0\n   0    0    0 1010    0   10    0    0    0    0    0 1001 1111\n1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111\n1111 1111 1111 1111 1111 1111    0    0    0    0    0    0    0\n   0    1    0    0    0    0    1  100    0   11    0    0    0\n   0    0    0 1000    0   10    0    0    0    0    0 1100 1111\n1111 1111 1111 1111 1010 1100 1111  110 1000    1 1010  111 1010\n<\/code><\/pre>\n<p><\/small><\/p>\n<p>It doesn&#8217;t really look like anything. If you kind of squint the different patterns look like different greyscale blobs. Kinda. I play around with some numbers and run it a bunch of times but I can&#8217;t make the it look like anything except gibberish.<\/p>\n<p>I end up tweaking the code to output either <code>X<\/code> or Space instead of the numbers (full program <a href=\"ambrosia-decode\/1\/decode.rb\">here<\/a>):<\/p>\n<pre><code class=\"language-ruby\">dump(level, 85, 22, 8) {|pix| pix==0 ? \" \" : \"X\"}\n<\/code><\/pre>\n<p>produces<\/p>\n<p><small><\/p>\n<pre><code>     X X  XX     X XX   X  XX  XX  XXXXXXXXXX   X  XX  XXXXXX   X  XX  XX  XXXXXXXXXXX\nXXXX  XX  XXXXXXXXXX  XX  XX  XXXXXXXXXXXXXX   X  XX  XXXXXXXXXXX  X  XX  XX  XXXXXXXX\nXXXXXXX   X  XX  XXXXXXXXXXX  X  XX  XX  XXXXXXXXXXXXXX   X  XX  XXXXXXXXXXX  X  XX  X\nXX  XXXXXXXXXXXX X  XX  XXXXXXXXXXX  X  XX  XX  XXXXXXXXXX  XX  XXXXXX   X  XX  XXXXXX\nXXXXX   X  XX  XXXXXXX  X  XX  XXXXXXX  X  XX  XXXXXXXXXX   X  XX  XX  XXXXXXXXXX  XX\n  XXXXXXXXXX   X  XX  XXXXXXXXXXXXXXXXXXXXXX  XX  XXXXXXXXXXX  X  XX  XXXXXXXXXX  XX\n XX  XXXXXXXXXX  XX  XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX X  XX  XXXXXXXXX\nXX  XX  XX  XXXXXXXXXX  XX  XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX  XX  XX\nXXXXXXXXX  XX  XX  XXXXXXXXXX  XX  XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n   X  XX  XXXXXXXXXX  XX  XX  XXXXXXXXXX  XX  XXXXXXXXXXXXXXXXXX  XX  XXXXXXXXXXXXXXXX\nXXXXXXXXXXXXXXXXXXX   X  XX  XXXXXXXXXX  XX  XX  XXXXXXXXXX  XX  XXXXXXXXXXXXXXXXXX  X\nXX  XXXXXXXXXXXXXXX  X  XX  XXXXXXXXXXXXXXXXXX   X  XX  XXXXXXXXXX  XX  XX  XXXXXXXXXX\nX  XX  XXXXXXXXXXXXXXXXXXXXXX   X  XX  XXXXXXXXXXXXXXX  X  XX  XXXXXXXXXXXXXXXXXX   X\n  XX  XXXXXXXXXX  XX  XX  XXXXXXXXXX  XX  XXXXXXXXXXXXXXXXXXXXXX   X  XX  XXXXXXXXXXXX\nXXX  XX  XXXXXXXXXXXXXXXXXX   X  XX  XXXXXXXXXX  XX  XX  XXXXXXXXXX  XX  XXXXXXXXXXXXX\nXXXXXXXXXX  XX  XXXXXXXXXXXX X  XX  XXXXXXXXXXXXXXXXXX  XX  XXXXXXXXXX  XX  XX  XXXXXX\nXXXXX   X  XX  XXXXXXXXXXXXXXXXXXXXXX   X  XX  XXXXXXXXXXX  X  XX  XXXXXXXXXXXXXXXXXX\n  XX  XXXXXXXXXX   X  XX  XX  XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX   X  XX  XXXXXXXXXX\n  X  XX  XXXXXXXXXXXXXXXXXX  XX  XXXXXXXXXXXXXX  XX  XX  XXXXXXXXXXXXXXXXXXXX X  XX  X\nXXXXXXXXXXXXXXX  X  XX  XXXXXXXXXX  XX  XXXXXXXXXXXXXXXX X  XX  XXXXXXXXXXXXXX  XX  XX\nX  XXXXXXXXXXXXXXXXXXX  X  XX  XXXXXXXXXXXXXX   X  XX  XXXXXXXX X  XX  XXXXXXXXXXXXXXX\nX  X  XX  XXXXXXXXXXXXXX  XX  XX  XXXXXXXXXXXXXXXXXXX  X  XX  XXXXXXXXXXXX X  XX  XXXX\nXXX   X  XX  XXXXXXXXXXXXXX  XX  XXXXXXXXXXXX X  XX  XX  XXXXXXXXXXXXXXXXXX   X  XX  X\n<\/code><\/pre>\n<p><\/small><\/p>\n<p>That doesn&#8217;t look like anything. I tweak more numbers but each time I have to edit the file then run it to get results. It feels slow. I need my turn-around time <em>faster<\/em>! I kinda want a little UI and maybe have it respond to keystrokes. I start to think a web app could be nice&#8230;<\/p>\n<p>So I rewrite those same little code chunks in javascript. Now that I have a browser I can drop <code>&lt;input&gt;<\/code> tags down, grab a canvas and draw actual rectangles of different colors instead of printing <code>X<\/code>s. I get it working and it&#8217;s still kinda slow, deleting those numbers and incrementing them\u2014sounds dumb but requires thought and I want to focus on looking for patterns not remembering how to type numbers quickly.<\/p>\n<p>The easy answer is to make &#8220;PgUp&#8221; and &#8220;PgDown&#8221; increment and decrement the number (and &#8220;Home&#8221; and &#8220;End&#8221; to increment and decrement by 10). This finally feels good. So I sit down and play:<\/p>\n<div class=\"resizer\"><iframe src=\"\/ambrosia-decode\/2\/decode.html\" class=\"level\"><\/iframe><\/div>\n<p>Can you get it to display something resembling the words &#8220;Level&#8221;? I sure couldn&#8217;t. I started thinking this probably wasn&#8217;t a straight bitmap.<\/p>\n<h2>Take 2<\/h2>\n<p>My next guess was some sort of run length encoding (&#8220;RLE&#8221;). This can be used to compress the data (very simply). My brother had another idea though\u2014he thought they might be PICT files. I wasn&#8217;t sure\u2014why wouldn&#8217;t they put them in PICT resources then? But I don&#8217;t remember how PICTs work so I couldn&#8217;t rule it out.<\/p>\n<p>Turns out the Inside Macintosh series of books described the PICT format. I still have mine on the shelf in the other room, but it&#8217;s much easier to just search google. Luckily, Apple still has them on their web site.<\/p>\n<p><a href=\"https:\/\/developer.apple.com\/library\/archive\/documentation\/mac\/QuickDraw\/QuickDraw-458.html\">Inside Mac Appendix about Quickdraw<\/a> had <a href=\"https:\/\/developer.apple.com\/library\/archive\/documentation\/mac\/QuickDraw\/QuickDraw-463.html#MARKER-9-252\">this example picture data<\/a>:<\/p>\n<pre><code>data 'PICT' (129) {\n$\"0078\"     \/* picture size; don't use this value for picture size *\/\n$\"0002 0002 006E 00AA\"  \/* bounding rectangle of picture *\/\n$\"0011\"     \/* VersionOp opcode; always $0011 for version 2 *\/\n$\"02FF\"     \/* Version opcode; always $02FF for version 2 *\/\n$\"0C00\"     \/* HeaderOp opcode; always $0C00 for version 2 *\/\n            \/* next 24 bytes contain header information *\/\n   $\"FFFF FFFF\"   \/* version; always -1 (long) for version 2 *\/\n   $\"0002 0000 0002 0000 00AA 0000 006E 0000\"   \/* fixed-point bounding \n                                                   rectangle for picture *\/\n   $\"0000 0000\"   \/* reserved *\/\n$\"001E\"     \/* DefHilite opcode to use default hilite color *\/\n$\"0001\"     \/* Clip opcode to define clipping region for picture *\/\n   $\"000A\"  \/* region size *\/\n   $\"0002 0002 006E 00AA\"  \/* bounding rectangle for clipping region *\/\n$\"000A\"     \/* FillPat opcode; fill pattern specifed in next 8 bytes *\/\n   $\"77DD 77DD 77DD 77DD\"  \/* fill pattern *\/\n$\"0034\"     \/* fillRect opcode; rectangle specified in next 8 bytes *\/\n   $\"0002 0002 006E 00AA\"  \/* rectangle to fill *\/\n$\"000A\"     \/* FillPat opcode; fill pattern specified in next 8 bytes *\/\n   $\"8822 8822 8822 8822\"  \/* fill pattern *\/\n$\"005C\"     \/* fillSameOval opcode *\/\n$\"0008\"     \/* PnMode opcode *\/\n$  \"0008\"   \/* pen mode data *\/\n$\"0071\"     \/* paintPoly opcode *\/\n   $\"001A\"  \/* size of polygon *\/\n   $\"0002 0002 006E 00AA\"  \/* bounding rectangle for polygon *\/\n   $\"006E 0002 0002 0054 006E 00AA 006E 0002\"   \/* polygon points *\/\n$\"00FF\"     \/* OpEndPic opcode; end of picture *\/\n}\n<\/code><\/pre>\n<p>Lets hex dump our <code>SpRd<\/code> data and see if it matches up. All these dumps are <a href=\"https:\/\/en.wikipedia.org\/wiki\/Endianness\">big endian<\/a>, as the all the old 680&#215;0 and PowerPC Macs were big endian machines (technically PowerPC CPUs could run in either little endian or big endian mode, but MacOS Classic always run in big-endian mode):<\/p>\n<figure id=\"sprd\">\n<pre><code class=\"language-annotated\">00000000: 0000 0000 0018 0055 0000 07d4 0000 0000  .......U........\n00000010: 0018 0055 0100 0000 [01]00 0024 [03]00 0003  ...U.......$.... =&gt; Suspicously aligned 1 #first-byte-1, Suspicously aligned 3 #first-byte-3\n00000020: [02]00 0009 ffff ffff ffff ffff ff00 0000  ................ =&gt; Suspiciously aligned 2 #first-byte-2\n00000030: [03]00 0040 [02]00 0005 ffff ffff ff00 0000  ...@............ =&gt; Suspicously aligned 3 #first-byte-3, Suspiciously aligned 2 #first-byte-2\n00000040: [01]00 0024 [03]00 0002 [02]00 000c ffff 5911  ...$..........Y. =&gt; Suspicously aligned 1 #first-byte-1, Suspicously aligned 3 #first-byte-3, Suspiciously aligned 2 #first-byte-2\n00000050: 3535 3535 5fff ffff [03]00 003b [02]00 0008  5555_......;.... =&gt; Suspicously aligned 3 #first-byte-3, Suspiciously aligned 2 #first-byte-2\n00000060: ffff ffff 5911 5fff [01]00 002c [03]00 0001  ....Y._....,.... =&gt; Suspicously aligned 1 #first-byte-1, Suspicously aligned 3 #first-byte-3\n00000070: [02]00 000d ffff 5f05 0411 3535 3535 3589  ......_...55555. =&gt; Suspiciously aligned 2 #first-byte-2\n00000080: ff00 0000 [03]00 0039 [02]00 000a ffff ff59  .......9.......Y =&gt; Suspicously aligned 3 #first-byte-3, Suspiciously aligned 2 #first-byte-2\n<\/code><\/pre>\n<\/figure>\n<aside>\nNote: Look for the &#8220;annotate&#8221; links sprinkled amongst the rest of the text. Click on them to highlight different parts of the most recent hex dump. Hover over the highlighted sections in the hex dump to see annotations.<br \/>\n<\/aside>\n<p>That doesn&#8217;t match up, so I decide to rule out PICTs.<\/p>\n<p>But I happen to notice a lot of <code>03<\/code>, <code>02<\/code>, and <code>01<\/code> in the high byte of <code>u32<\/code> aligned words (<a href=\"javascript:enable_annotation(&quot;sprd-first-byte-1&quot;,&quot;sprd-first-byte-2&quot;,&quot;sprd-first-byte-3&quot;)\">annotate<\/a>).<\/p>\n<p>I try dumping the data with <code>xxd -c 4 -g 4<\/code> (group by 4, 4 bytes per line):<\/p>\n<pre><code class=\"language-annotated\">00000000: 00000000  ....\n00000004: 00180055  ...U\n00000008: 000007d4  ....\n0000000c: 00000000  ....\n00000010: 00180055  ...U\n00000014: [01]000000  .... =&gt; First Byte == 1\n00000018: [01]000024  ...$ =&gt; First Byte == 1\n0000001c: [03]000003  .... =&gt; First Byte == 3\n00000020: [02]000009  .... =&gt; First Byte == 2\n00000024: ffffffff  ....\n00000028: ffffffff  ....\n0000002c: ff000000  ....\n00000030: [03]000040  ...@ =&gt; First Byte == 3\n00000034: [02]000005  .... =&gt; First Byte == 2\n00000038: ffffffff  ....\n0000003c: ff000000  ....\n00000040: [01]000024  ...$ =&gt; First Byte == 1\n00000044: [03]000002  .... =&gt; First Byte == 3\n00000048: [02]00000c  .... =&gt; First Byte == 2\n0000004c: ffff5911  ..Y.\n<\/code><\/pre>\n<p>Full listing <a href=\"\/ambrosia-decode\/Level.xxd-c4-g4\">here<\/a><\/p>\n<p>That looks more promising. In fact the entire thing seems to be <code>u32<\/code> aligned and the first bytes seem mostly similar.<\/p>\n<p>The <code>01<\/code>, <code>02<\/code>, <code>03<\/code> words don&#8217;t start right away, which means there&#8217;s probably a file header up front.<\/p>\n<p>I remember that the <code>SpRt<\/code> resource for the &#8220;Level&#8221; sprite says the width is &#8220;85&#8221;\u2014that&#8217;s is <code>0x55<\/code> in hex. I see that in the first few words a couple times:<\/p>\n<figure id=\"header-theory-1\">\n<pre><code class=\"language-annotated\">00000000: [00000000]  .... =&gt; Unknown zeros #zeros\n00000004: [0018][0055]  ...U =&gt; Height #height,Width\n00000008: [000007d4]  .... =&gt; Unknown length #length\n[0000000c]: [00000000]  .... =&gt; Offset the length is based from #offset, Unknown zeros #zeros\n00000010: [0018][0055]  ...U =&gt; Height (Again!) #height,Width (Again!)\n<\/code><\/pre>\n<\/figure>\n<p><code>0x18<\/code> is 24 decimal, which is what we think the height of the sprites is (<a href=\"javascript:enable_annotation(&quot;header-theory-1-height&quot;)\">annotate<\/a>). I got 22 by pixel counting, but 24 is close enough. There are some zeros that I don&#8217;t understand yet (<a href=\"javascript:enable_annotation(&quot;header-theory-1-zeros&quot;)\">annotate<\/a>), and a <code>0x7d4<\/code> (<a href=\"javascript:enable_annotation(&quot;header-theory-1-length&quot;)\">annotate<\/a>). That looks big enough to maybe be a length. I <code>ls -l<\/code> the file which says it&#8217;s 2016 bytes long, which is <code>0x7e0<\/code>. That&#8217;s extremely close to <code>0x7d4<\/code>, so it&#8217;s probably related. In fact, if we subtract them to see just how close they are then we get 12, or <code>0xc<\/code>. Aha! That corresponds to the file offset just after the length (<a href=\"javascript:enable_annotation(&quot;header-theory-1-offset&quot;);rename_annotation(&quot;header-theory-1-length&quot;,&quot;File%20length%20when%20added%20to%20the%20next%20offset&quot;)\">annotate<\/a>), which means that is the length of the rest of the file.<\/p>\n<p>So far the theory is that this is a header with some zeros that are unknown, a height and width, the length of the file (measure from just after reading that u32), and then the zeros and width and height again for some reason. Let&#8217;s not worry about that for now, let&#8217;s look at the rest.<\/p>\n<p>After staring at the data for a while I notice that whenever there are words that <em>don&#8217;t<\/em> start with <code>01<\/code>, <code>02<\/code>, or <code>03<\/code>, that they <em>always<\/em> come after a <code>02<\/code>. If we take some random sections of the file and look at them maybe we can see a pattern:<\/p>\n<figure id=\"header-02\">\n<pre><code class=\"language-annotated\">00000020: [02][000009]  .... =&gt; Command 2, Length = 9 #data_l\n00000024: [ffffffff]  .... =&gt; Data #data_b\n00000028: [ffffffff]  .... =&gt; Data #data_b\n0000002c: [ff][000000]  .... =&gt; Data #data_b, Padding for alignment #align\n\n000000e8: [02][00000a]  .... =&gt; Command 2, Length = 0xa (10) #data_l\n000000ec: [ff5f3535]  ._55 =&gt; Data #data_b\n000000f0: [0b033589]  ..5. =&gt; Data #data_b\n000000f4: [89ff][0000]  .... =&gt; Data #data_b, Padding for alignment #align\n\n000003f0: [02][000008]  .... =&gt; Command 2, Length = 8 #data_l\n000003f4: [ff3b3b3b]  .;;; =&gt; Data #data_b\n000003f8: [898989ff]  .... =&gt; Data #data_b\n\n0000055c: [02][000015]  .... =&gt; Command 2, Length = 0x15 (21) #data_l\n00000560: [ffff1dda]  .... =&gt; Data #data_b\n00000564: [8fffff8f]  .... =&gt; Data #data_b\n00000568: [dbdcdcdc]  .... =&gt; Data #data_b\n0000056c: [dbdaffff]  .... =&gt; Data #data_b\n00000570: [ffff03d9]  .... =&gt; Data #data_b\n00000574: [ff][000000]  .... =&gt; Data #data_b, Padding for alignment #align\n<\/code><\/pre>\n<\/figure>\n<p>The longer the trailing data is (until the next <code>01<\/code> or <code>03<\/code>), the larger that lower byte in the <code>02<\/code> u32. In fact, I count them and it looks like that is a precise byte count (<a href=\"javascript:enable_annotation(&quot;header-02-data_l&quot;,&quot;header-02-data_b&quot;)\">annotate<\/a>). When it doesn&#8217;t align to 32 bits it&#8217;s filled with zeros (<a href=\"javascript:enable_annotation(&quot;header-02-align&quot;)\">annotate<\/a>)! Skimming through the file I don&#8217;t see anything that counters this. So lets code it up and see if it&#8217;s really true. I decide to call these <code>u32<\/code>s that start with <code>01<\/code>, <code>02<\/code> and <code>03<\/code> (and the <code>00<\/code> I notice at the very end) &#8220;commands&#8221;, though I don&#8217;t know exactly what they are yet.<\/p>\n<p>I need to add a couple helper functions to the Bits class (which got converted to Javascript) to read bytes in a more file like way:<\/p>\n<pre><code class=\"language-javascript\">seek(offset) { this.cursor = offset }\ntell() { return this.cursor }\nread(bytes) {\n    let b = this.bytes.slice(this.cursor, this.cursor+bytes)\n    this.cursor += bytes\n    return b;\n}\nreadn(n) { return this.read(n).reduce((acc, b) =&gt; acc &lt;&lt; 8 | b, 0) }\nread8()  { return this.readn(1) }\nread16() { return this.readn(2) }\nread24() { return this.readn(3) }\nread32() { return this.readn(4) }\n<\/code><\/pre>\n<p>and an ambitiously titled <code>unpack()<\/code> (ambitious because it does nothing of the sort) to test the theory:<\/p>\n<pre><code class=\"language-javascript\">function unpack(data) {\n    console.log(\"header\")\n    console.log(`  x,y?   ${data.read16()},${data.read16()}`)\n    console.log(`  height ${data.read16()}`)\n    console.log(`  width  ${data.read16()}`)\n    let length = data.read32()\n    console.log(`  length ${dec_and_hex(length)} ` +\n                   `(end:${dec_and_hex(data.tell() + length)})`)\n    console.log(`  again x,y?   ${data.read16()},${data.read16()}`)\n    console.log(`  again height ${data.read16()}`)\n    console.log(`  again width  ${data.read16()}`)\n\n    let c = 0\n    while (true) {\n        let command = data.read(4)\n        console.log(`command[${c} (${hex(data.tell())})] ${command[0]} ` +\n                        `[${hex(...command)}]`)\n        if (command[0] == 0) break;\n        if (command[0] == 2) {\n            let chunk = data.read(command[3]);\n            \/\/ this is wrong if len == 0: -1%4 =&gt; -1 in js. yuck.\n            let align = data.read((3 - ((command[3]-1) % 4)));\n            console.log(`    chunk  ${hex(...chunk)} | ${hex(...align)}`);\n        }\n        c += 1\n    }\n\n    console.log(`end @ ${hex(data.tell())}`)\n}\n<\/code><\/pre>\n<p><a href=\"\/ambrosia-decode\/3\/decode.js\">Full code here<\/a>.<\/p>\n<p>The output looks like:<\/p>\n<pre><code>header\n  x,y?   0,0\n  height 24\n  width  85\n  length 2004 [7d4] (end:2016 [7e0])\n  again x,y?   0,0\n  again height 24\n  again width  85\ncommand[0 (18)] 1 [01 00 00 00]\ncommand[1 (1c)] 1 [01 00 00 24]\ncommand[2 (20)] 3 [03 00 00 03]\ncommand[3 (24)] 2 [02 00 00 09]\n    chunk  ff ff ff ff ff ff ff ff ff | 00 00 00\ncommand[4 (34)] 3 [03 00 00 40]\ncommand[5 (38)] 2 [02 00 00 05]\n    chunk  ff ff ff ff ff | 00 00 00\ncommand[6 (44)] 1 [01 00 00 24]\ncommand[7 (48)] 3 [03 00 00 02]\ncommand[8 (4c)] 2 [02 00 00 0c]\n    chunk  ff ff 59 11 35 35 35 35 5f ff ff ff |\n\n[... snip ...]\n\ncommand[189 (7bc)] 3 [03 00 00 0b]\ncommand[190 (7c0)] 2 [02 00 00 08]\n    chunk  ff ff ff ff ff ff ff ff |\ncommand[191 (7cc)] 3 [03 00 00 06]\ncommand[192 (7d0)] 2 [02 00 00 08]\n    chunk  ff ff ff ff ff ff ff ff |\ncommand[193 (7dc)] 1 [01 00 00 00]\ncommand[194 (7e0)] 0 [00 00 00 00]\nend @ 7e0\n<\/code><\/pre>\n<p><a href=\"\/ambrosia-decode\/decode3.js.out\">Full output here<\/a>.<\/p>\n<p>Hey, that theory looks correct. It even ends on a nice <code>00<\/code> command as some kind of null terminator.<\/p>\n<p>But what are <code>01<\/code> and <code>03<\/code> for??<\/p>\n<figure id=\"header-01-03\">\n<pre><code class=\"language-annotated\">000000e4: [03][000039]  ...9 =&gt; Command 3, Repeat count #repeat_l\n000000e8: [02][00000a]  .... =&gt; Command 2, Data count #+data_l\n000000ec: [ff5f3535]  ._55 =&gt; Data #+data_b\n000000f0: [0b033589]  ..5. =&gt; Data #+data_b\n000000f4: [89ff][0000]  .... =&gt; Data #+data_b, Padding for alignment #+align\n000000f8: [01][000028]  ...( =&gt; Command 1, Skip count? #skip_l\n000000fc: [03][000002]  .... =&gt; Command 3, Repeat count? #repeat_l\n00000100: [02][00000b]  .... =&gt; Command 2, Data count #+data_l\n00000104: [ffffff59]  ...Y =&gt; Data #+data_b\n00000108: [35358389]  55.. =&gt; Data #+data_b\n0000010c: [89ffff][00]  .... =&gt; Data #+data_b, Padding for alignment #+align\n00000110: [03][00003a]  ...: =&gt; Command 3, Repeat count? #repeat_l\n00000114: [02][00000a]  .... =&gt; Command 2, Data count #+data_l\n00000118: [ffff895f]  ..._ =&gt; Data #+data_b\n0000011c: [35115f89]  5._. =&gt; Data #+data_b\n00000120: [89ff][0000]  .... =&gt; Data #+data_b, Padding for alignment #+align\n00000124: [01][000068]  ...h =&gt; Command 1, Skip count? #skip_l\n00000128: [03][000004]  .... =&gt; Command 3, Repeat count? #repeat_l\n0000012c: [02][000008]  .... =&gt; Command 2, Data count #+data_l\n00000130: [ff353535]  .555 =&gt; Data #+data_b\n00000134: [838989ff]  .... =&gt; Data #+data_b\n00000138: [03][00000f]  .... =&gt; Command 3, Repeat count? #repeat_l\n0000013c: [02][000005]  .... =&gt; Command 2, Data count #+data_l\n<\/code><\/pre>\n<\/figure>\n<p>Since I suspect it&#8217;s some sort of <abbr title=\"Run Length Encoding\">RLE<\/abbr>, I start thinking along those lines. With a run length encoding you usually have some sort of &#8220;repeat&#8221; code. I notice that the <code>03<\/code> command always comes before a <code>02<\/code> command. So I make a guess. I think maybe <code>03<\/code> is a &#8220;repeat&#8221; command (<a href=\"javascript:enable_annotation(&quot;header-01-03-repeat_l&quot;);rename_annotation(&quot;header-01-03-command-3&quot;,&quot;Repeat?&quot;)\">annotate<\/a>). But then what is <code>01<\/code>? I think that may be a &#8220;skip&#8221; command (<a href=\"javascript:enable_annotation(&quot;header-01-03-skip_l&quot;);rename_annotation(&quot;header-01-03-command-1&quot;,&quot;Skip?&quot;)\">annotate<\/a>)\u2014it&#8217;s kind of sporadic but it <em>always<\/em> comes before the repeat. And the <code>02<\/code> command must be verbatim data (<a href=\"javascript:rename_annotation(&quot;header-01-03-command-2&quot;,&quot;Verbatim%20Data?&quot;)\">annotate<\/a>). Well\u2026 Just theorizing gets us nowhere, lets try it out!<\/p>\n<p>First I need another couple functions in the <code>Bits<\/code> class, one for writing and a quickie hex dump function to check my work&#8230;<\/p>\n<pre><code class=\"language-javascript\">write(data) {\n    this.bytes.splice(this.cursor, data.length, ...data)\n    this.cursor += data.length\n}\ndump() {\n    return this.bytes.reduce((acc,b,i) =&gt; { let ch = Math.floor(i\/16);\n                                            acc[ch] = [...(acc[ch]??[]), b];\n                                            return acc }, [])\n        .map(row =&gt; row.map(b =&gt; hex(b)).join(\", \") + \"\\n\")\n        .join('');\n}\n<\/code><\/pre>\n<p>The <code>write()<\/code> function replaces the bytes at the cursor with the data passed in (it does not insert).<\/p>\n<p>Now I modify up the <code>unpack()<\/code> function (which is finally living up to its name!):<\/p>\n<pre><code class=\"language-javascript\">function unpack(data) {\n    let out = new Bits()\n    console.log(\"header\")\n    console.log(`  x,y?   ${data.read16()},${data.read16()}`)\n    console.log(`  height ${data.read16()}`)\n    console.log(`  width  ${data.read16()}`)\n    let length = data.read32()\n    console.log(`  length ${dec_and_hex(length)} ` +\n                   `(end:${dec_and_hex(data.tell() + length)})`)\n    console.log(`  again x,y?   ${data.read16()},${data.read16()}`)\n    console.log(`  again height ${data.read16()}`)\n    console.log(`  again width  ${data.read16()}`)\n\n    let c = 0\n    let repeat;\n    while (true) {\n        let command = data.read(4)\n        console.log(`command[${c} (${hex(data.tell())})] ${command[0]} ` +\n                        `[${hex(...command)}]`)\n        if (command[0] == 0) break;\n        if (command[0] == 1) \/\/ skip forward (write zeros)?\n            out.write(Array(command[3]).fill(0))\n        else if (command[0] == 3) \/\/ repeat the next command n times?\n            repeat = command[3]\n        else if (command[0] == 2) {\n            let chunk = data.read(command[3]);\n            \/\/ this is wrong if len == 0: -1%4 =&gt; -1 in js. yuck.\n            let align = data.read((3 - ((command[3]-1) % 4)));\n            console.log(`    chunk  ${hex(...chunk)} | ${hex(...align)}`);\n            out.write(Array(repeat).fill(chunk).flat())\n        }\n        c += 1\n    }\n\n    console.log(`end @ ${hex(data.tell())}`)\n\n    console.log(out.dump())\n    return out;\n}\n<\/code><\/pre>\n<p>I get some data out! Lets graph it to see if it works:<\/p>\n<div class=\"resizer\"><iframe src=\"\/ambrosia-decode\/4\/decode.html\" class=\"level\"><\/iframe><\/div>\n<p>Play around, can you find the image?<\/p>\n<p>&#91;spoiler&#93; I couldn&#8217;t either. \ud83d\ude41<\/p>\n<p>Hmm. I start looking at the output, and it just doesn&#8217;t make any sense. First off it&#8217;s way too big\u201485&#215;24 at 8 bits per pixel is 2040 bytes and the output is 9627 bytes long. And that&#8217;s not off by a good multiple either. Second off, the repeat counts on some stuff is <em>really<\/em> high which seems wrong.<\/p>\n<h2>Back to the drawing board<\/h2>\n<p>I decide this is just wrong and stare at the <a href=\"\/ambrosia-decode\/Level.xxd-c4-g4\">&#8220;command list&#8221;<\/a> again. My brother and I started screen sharing so we could have 2 heads working on the problem. I really wanted to figure out what the <code>01<\/code> commands are for. That first one with zeros is weird, and there&#8217;s another one like that at the end. So my initial &#8220;skip&#8221; thought doesn&#8217;t really make sense because why skip zero? What could its number mean? We search through the file and notice the number after the <code>01<\/code> command generally gets bigger as we go farther into the file. On a whim I go through and stick a newline before each <code>01<\/code> command. It ends up looking like this:<\/p>\n<figure id=\"take-2\">\n<pre><code class=\"language-annotated\">00000000: [00000000]  .... =&gt; Unknown zeros #+zeros\n00000004: [0018][0055]  ...U =&gt; Height, Width\n00000008: [000007d4]  .... =&gt; File length #+length\n[0000000c]: [00000000]  .... =&gt; Offset the length is based from #+offset, Unknown zeros #+zeros\n00000010: [0018][0055]  ...U =&gt; Height (Again!) #+height, Width (Again!) #+width\n\n00000014: [01][000000]  .... =&gt; Command 1, Length (0 + 0x00000018) #command_1_l\n\n[00000018]: [01][000024]  ...$ =&gt; Offset the above length is based from #command_1_o, Command 1, Length (0x24 + 0x0000001c == 0x00000040) #command_1_l\n[0000001c]: 03000003  .... =&gt; Offset the above length is based from #command_1_o\n00000020: 02000009  ....\n00000024: ffffffff  ....\n00000028: ffffffff  ....\n0000002c: ff000000  ....\n00000030: 03000040  ...@\n00000034: 02000005  ....\n00000038: ffffffff  ....\n0000003c: ff000000  ....\n\n00000040: [01][000024]  ...$ =&gt; Command 1, Length (0x24 + 0x00000044 = 0x00000068) #command_1_l\n[00000044]: 03000002  .... =&gt; Offset the above length is based from #command_1_o\n00000048: 0200000c  ....\n0000004c: ffff5911  ..Y.\n00000050: 35353535  5555\n00000054: 5fffffff  _...\n00000058: 0300003b  ...;\n0000005c: 02000008  ....\n00000060: ffffffff  ....\n00000064: 59115fff  Y._.\n\n[... snip ...]\n\n0000077c: [01][000058]  ...X =&gt; Command 1, Length (0x58 + 0x00000780 = 0x7d8) #command_1_l\n[00000780]: 03000002  .... =&gt; Offset the above length is based from #command_1_o\n00000784: 02000011  ....\n00000788: ffffffff  ....\n0000078c: ffffffff  ....\n00000790: ffffffff  ....\n00000794: ffffffff  ....\n00000798: ff000000  ....\n0000079c: 03000007  ....\n000007a0: 02000007  ....\n000007a4: ffffffff  ....\n000007a8: ffffff00  ....\n000007ac: 0300000b  ....\n000007b0: 02000004  ....\n000007b4: ffffffff  ....\n000007b8: 0300000b  ....\n000007bc: 02000008  ....\n000007c0: ffffffff  ....\n000007c4: ffffffff  ....\n000007c8: 03000006  ....\n000007cc: 02000008  ....\n000007d0: ffffffff  ....\n000007d4: ffffffff  ....\n\n000007d8: [01][000000]  .... =&gt; Command 1, Length (0 + 0x000007dc) #command_1_l\n\n[000007dc]: 00000000  .... =&gt; Offset the above length is based from #command_1_o\n<\/code><\/pre>\n<\/figure>\n<p>Interestingly, not only do the numbers get bigger through the file, the distance between <code>01<\/code> commands gets bigger, too. Also I notice those numbers are very round, the lower nibble of every <code>01<\/code> command is <code>0<\/code>, <code>4<\/code>, <code>8<\/code>, or <code>c<\/code>. That&#8217;s <code>u32<\/code> aligned. It must be a length! Sure enough, That first non-zero chunk has a <code>0x24<\/code>. If we add that to the next address (same as the length in the header) we get <code>0x24+0x1c =&gt; 0x40<\/code>, which is exactly the address where the next <code>01<\/code> commands starts (<a href=\"javascript:enable_annotation(&quot;take-2-command_1_l&quot;,&quot;take-2-command_1_o&quot;)\">annotate<\/a>). Eureka! We spot check a few more and they all agree with this.<\/p>\n<p>Still, what does <code>01<\/code> signify? It has a length like it&#8217;s wrapping something, but what concept is it wrapping? I decided to see how many there are in the file. There are 24 including the ones with <code>00<\/code> length. Hey! 24 is the height! These are <em>lines<\/em>! (<a href=\"javascript:rename_annotation(&quot;take-2-command-1&quot;,&quot;Line%20Command&quot;)\">annotate<\/a>) And that makes some sense, too: earlier I said I pixel counted and got 22 pixels on the level font, but the file said it was 24 high. The reason for that discrepancy is 2 blank lines, and <code>00<\/code> length makes a lot of sense for a blank line.<\/p>\n<p>So that seems like very strong evidence to me but we can&#8217;t decode without figuring out what the other commands do. I&#8217;m pretty sure I was right about the <code>02<\/code> command meaning &#8220;verbatim data&#8221;, but what is the <code>03<\/code>? Well, let&#8217;s look at the first non-blank line again:<\/p>\n<figure id=\"line-1\">\n<pre><code class=\"language-annotated\">00000018: [01][000024]  ...$ =&gt; Line Command, Line length (0x24 + 0x0000001c == 0x00000040) #+command_1_l\n0000001c: [03][000003]  .... =&gt; Command 3 #+skip, Skip count #skip_c\n00000020: [02][000009]  .... =&gt; Verbatim Data Command #+data, Verbatim Data Length #+data_l\n00000024: [ffffffff]  .... =&gt; Data #+data_b\n00000028: [ffffffff]  .... =&gt; Data #+data_b\n0000002c: [ff][000000]  .... =&gt; Data #+data_b, Padding #+align\n00000030: [03][000040]  ...@ =&gt; Command 3 #+skip, Skip count #skip_c\n00000034: [02][000005]  .... =&gt; Verbatim Data Command #+data, Verbatim Data Length #+data_l\n00000038: [ffffffff]  .... =&gt; Data #+data_b\n0000003c: [ff][000000]  .... =&gt; Data #+data_b, Padding #+align\n<\/code><\/pre>\n<\/figure>\n<p>I have a guess\u2026 I think it&#8217;s the skip (which I previously thought was command <code>01<\/code>). (<a href=\"javascript:enable_annotation(&quot;line-1-skip_c&quot;);rename_annotation(&quot;line-1-skip&quot;,&quot;Skip&quot;);\">annotate<\/a>) Before coding we can do a spot check to see if this seems correct: We can look at the commands in this line and see if they make sense.<\/p>\n<p>That would be: <span class=\"hover\" data-target=\"line-1.skip_c.0\">skip <code>0x3<\/code><\/span>, <span class=\"hover\" data-target=\"line-1.data_l.0\">verbatim <code>0x9<\/code><\/span>, <span class=\"hover\" data-target=\"line-1.skip_c.1\">skip <code>0x40<\/code><\/span>, <span class=\"hover\" data-target=\"line-1.data_l.1\">verbatim <code>0x5<\/code><\/span>. In total that&#8217;s <code>0x51<\/code>, which is 81 in decimal. That&#8217;s very close to the width of 85! It&#8217;s even kind of centered comparing against the first skip (3 vs 4). So that&#8217;s looking promising, let&#8217;s code it up!<\/p>\n<pre><code class=\"language-javascript\">function unpack(data) {\n    console.log(\"header\")\n    console.log(`  x,y?   ${data.read16()},${data.read16()}`)\n    console.log(`  height ${data.read16()}`)\n    console.log(`  width  ${data.read16()}`)\n    let length = data.read32()\n    console.log(`  length ${dec_and_hex(length)} ` +\n                   `(end:${dec_and_hex(data.tell() + length)})`)\n    console.log(`  again x,y?   ${data.read16()},${data.read16()}`)\n    let height = data.read16();\n    let width = data.read16();\n    console.log(`  again height ${height}`)\n    console.log(`  again width  ${width}`)\n\n    let out = new Bits(Array(width * height).fill(0))\n\n    let c = 0\n    let repeat;\n    let line = -1\n    while (true) {\n        let command = data.read(4)\n        console.log(`command[${c} (${hex(data.tell())})] ${command[0]} [${hex(...command)}]`)\n        if (command[0] == 0) break;\n        else if (command[0] == 1) { \/\/ Line header\n            line += 1\n            let sol = line * width\n            out.seek(sol)\n        } else if (command[0] == 3) { \/\/ Skip forward (write zeros)?\n            out.write(Array(command[3]).fill(0))\n        } else if (command[0] == 2) {\n            let chunk = data.read(command[3])\n            \/\/ this is wrong if len == 0: -1%4 =&gt; -1 in js. yuck.\n            let align = data.read((3 - ((command[3]-1) % 4)));\n            out.write(chunk)\n        }\n        c += 1\n    }\n    return out;\n}\n<\/code><\/pre>\n<p>And that produces\u2026<\/p>\n<div class=\"resizer\"><iframe src=\"\/ambrosia-decode\/5\/decode.html\" class=\"level\"><\/iframe><\/div>\n<p><i>Woohoo! Success!<\/i><\/p>\n<p>&#8220;But what about the colors&#8221;, my brother asks. Oh. Well, that&#8217;s probably not too hard\u2026<\/p>\n<h2>Color<\/h2>\n<p>I explain about color lookup tables (often called &#8220;cluts&#8221;) and we find one named &#8220;standard&#8221; in a resource file. He dumps it to binary and we take a look with <code>xxd -c 8<\/code>:<\/p>\n<figure id=\"clut\">\n<pre><code class=\"language-annotated\">00000000: [0000 0000] [0000 00ff]  ........ =&gt; First color index #zeros, Last color index #max_color\n00000008: [0000] [ffff] [ffff] [ffff]  ........ =&gt; Color index #index, Red #red, Green #green, Blue #blue\n00000010: [0001] [ffff] [ffff] [cccc]  ........ =&gt; Color index #index, Red #red, Green #green, Blue #blue\n00000018: [0002] [ffff] [ffff] [9999]  ........ =&gt; Color index #index, Red #red, Green #green, Blue #blue\n00000020: [0003] [ffff] [ffff] [6666]  ......ff =&gt; Color index #index, Red #red, Green #green, Blue #blue\n00000028: [0004] [ffff] [ffff] [3333]  ......33 =&gt; Color index #index, Red #red, Green #green, Blue #blue\n00000030: [0005] [ffff] [ffff] [0000]  ........ =&gt; Color index #index, Red #red, Green #green, Blue #blue\n00000038: [0006] [ffff] [cccc] [ffff]  ........ =&gt; Color index #index, Red #red, Green #green, Blue #blue\n00000040: [0007] [ffff] [cccc] [cccc]  ........ =&gt; Color index #index, Red #red, Green #green, Blue #blue\n00000048: [0008] [ffff] [cccc] [9999]  ........ =&gt; Color index #index, Red #red, Green #green, Blue #blue\n[ ... ]\n000007d8: [00fa] [7777] [7777] [7777]  ..wwwwww =&gt; Color index #index, Red #red, Green #green, Blue #blue\n000007e0: [00fb] [5555] [5555] [5555]  ..UUUUUU =&gt; Color index #index, Red #red, Green #green, Blue #blue\n000007e8: [00fc] [4444] [4444] [4444]  ..DDDDDD =&gt; Color index #index, Red #red, Green #green, Blue #blue\n000007f0: [00fd] [2222] [2222] [2222]  ..\"\"\"\"\"\" =&gt; Color index #index, Red #red, Green #green, Blue #blue\n000007f8: [00fe] [1111] [1111] [1111]  ........ =&gt; Color index #index, Red #red, Green #green, Blue #blue\n00000800: [00ff] [0000] [0000] [0000]  ........ =&gt; Color index #index, Red #red, Green #green, Blue #blue\n<\/code><\/pre>\n<\/figure>\n<p><a href=\"\/ambrosia-decode\/Standard_clut-xxd-c8\">Full output here<\/a>.<\/p>\n<p>That looks pretty straightforward. A u32 0 (probably the minimum color number), a u32 max color (255 in this case), and then each entry is <code>u16[4]: index, r, g, b<\/code>. (<a href=\"javascript:enable_annotation(&quot;clut-zeros&quot;,&quot;clut-max_color&quot;,&quot;clut-index&quot;,&quot;clut-red&quot;,&quot;clut-green&quot;,&quot;clut-blue&quot;)\">annotate<\/a>) So we write a little clut decoder:<\/p>\n<pre><code class=\"language-javascript\">function load_clut(inc) {\n    inc.read(7);\n    let colors = inc.readn(1);\n    let table = Array(colors);\n    while (inc.tell() &lt; inc.length()) {\n        inc.read8();\n        let c = inc.read8();\n        table[c] = { r: inc.read16() \/ 65535 * 255,\n                     g: inc.read16() \/ 65535 * 255,\n                     b: inc.read16() \/ 65535 * 255 }\n    }\n    return table;\n}\n<\/code><\/pre>\n<p>And then use the clut in the draw function instead of just making up a greyscale value:<\/p>\n<pre><code class=\"language-diff\">diff a\/main.js b\/main.js\n--- a\/main.js\n+++ b\/main.js\n@@ -12,7 +13,7 @@ function erase() {\n     view.clearRect(0,0, view_el.width, view_el.height);\n }\n\n-function draw(data, width, height, bpp, start=0, stride=undefined) {\n+function draw(clut, data, width, height, bpp, start=0, stride=undefined) {\n     if (stride==undefined)\n         stride = width * bpp;\n     else\n@@ -22,8 +23,7 @@ function draw(data, width, height, bpp, start=0, stride=undefined) {\n     for (let y=0; y&lt;height; y++) {\n         for (let x=0; x&lt;width; x++) {\n             let pix=data.get(start*8 + y * stride + x * bpp, bpp);\n-            let pix255=pix*255\/(2**bpp-1);\n-            view.fillStyle = `rgb(${pix255},${pix255},${pix255})`;\n+            view.fillStyle = `rgb(${clut[pix].r},${clut[pix].g},${clut[pix].b})`;\n             view.fillRect(x*zoom, y*zoom, zoom, zoom);\n         }\n     }\n@@ -40,13 +40,15 @@ function main() {\n     let level_packed = new Bits(level_sprd);\n     let level = unpack(level_packed);\n\n+    let clut = load_clut(new Bits(Standard_clut));\n+\n     let width, height, bpp, offset;\n     let go = (event) =&gt; {\n         width = +el.width.value;\n         height = +el.height.value;\n         bpp = +el.bpp.value;\n         offset = +el.offset.value;\n-        draw(level, width, height, bpp, offset);\n+        draw(clut, level, width, height, bpp, offset);\n     };\n     Object.values(el).forEach(el =&gt; el.oninput = go);\n<\/code><\/pre>\n<p>And\u2026<\/p>\n<div class=\"resizer\"><iframe src=\"\/ambrosia-decode\/6\/decode.html\" class=\"level\"><\/iframe><\/div>\n<p><i>Success again!<\/i><\/p>\n<h2>Wait, it&#8217;s not done??<\/h2>\n<p>Now that we&#8217;ve gotten &#8220;Level&#8221; to render, we plug in the snake head sprite in its place:<\/p>\n<div class=\"resizer\"><iframe src=\"\/ambrosia-decode\/7\/decode.html\" class=\"head wide\"><\/iframe><\/div>\n<p>Uh oh! Why one head? It&#8217;s supposed to have a bunch of frames of animation\u2026 And why is it so wide?<\/p>\n<p>Time to dive back into the command stream, but let&#8217;s just start with the header:<\/p>\n<figure id=\"header-anim\">\n<pre><code class=\"language-annotated\">00000000: [0000][0000]  ....   =&gt; Unknown zeros #+b-top, Unknown zeros #+b-left,\n00000004: [0018][0018]  ....   =&gt; Height #+b-bottom, Width #+b-right\n00000008: [00000250]  ...P     =&gt; File length #+length\n[0000000c]: [0000][0113]  .... =&gt; Offset the length is based from #+offset, Unknown zeros #+d-top, Wait those aren't zeros! #+d-left\n00000010: [0018][012b]  ...+   =&gt; Height (Again!) #+d-bottom, Wait that's not the width! #+d-right\n<\/code><\/pre>\n<\/figure>\n<p>Right off the bat it&#8217;s different. The other header had the length\/width repeated twice, this one has totally different stuff the 2nd time. Also That <code>0x250<\/code> length doesn&#8217;t look quite right: the file is 64K and that&#8217;s nowhere close to it. This header must just represent one animation frame and not the whole file (<a href=\"javascript:rename_annotation(&quot;header-anim-length&quot;,&quot;Frame%20Length&quot;)\">annotate<\/a>). But what is with those 2nd sets of numbers?<\/p>\n<p>Previously I kind of skimmed over the zeros in the first and fourth <code>u32<\/code>s since they didn&#8217;t really seem to affect anything, but now one of them was a <span class=\"hover\" data-target=\"header-anim.d-left.0\"><code>0x113<\/code><\/span>. And why is the width <span class=\"hover\" data-target=\"header-anim.d-right.0\"><code>0x12b<\/code> (299 decimal)<\/span>? I started thinking it may be a stride\u2014an offset between rows somehow. That kind of explained why the sprite with a single frame had the stride equal to the width. But then what was the <span class=\"hover\" data-target=\"header-anim.d-left.0\"><code>0x113<\/code> (275 decimal)<\/span> number? Some sort of vertical stride\/interleave number? Neither of those seemed reasonable. We pursued a bunch of leads here, adding and subtracting offsets from all over the file trying to make things line up. And then at one point it suddenly dawned on us:<span class=\"hover\" data-target=\"header-anim.d-right.0\"> <code>0x12b<\/code><\/span> &#8211;<span class=\"hover\" data-target=\"header-anim.d-left.0\"><code>0x113<\/code><\/span> was <span class=\"hover\" data-target=\"header-anim.b-right.0\"><code>0x18<\/code><\/span>. Then it struck me\u2014that&#8217;s not an (x,y) offset plus (width,height) that I was assuming\u2014it&#8217;s the old Mac standard way of specifying a rectangle <code>(top, left, bottom, right)<\/code>! (<a href=\"javascript:rename_annotation(&quot;header-anim-d-top&quot;,&quot;Destination%20rect%20top&quot;);rename_annotation(&quot;header-anim-d-left&quot;,&quot;Destination%20rect%20left&quot;);rename_annotation(&quot;header-anim-d-bottom&quot;,&quot;Destination%20rect%20bottom&quot;);rename_annotation(&quot;header-anim-d-right&quot;,&quot;Destination%20rect%20right&quot;);\">annotate<\/a>) Aha, and that explained the zeros in the first <code>u32<\/code>. Those first 4 <code>u16<\/code>s are <em>also<\/em> a rectangle. They must be the bounds of the sprite, and the second rectangle must be the destination in the sprite sheet they&#8217;re building (<a href=\"javascript:rename_annotation(&quot;header-anim-b-top&quot;,&quot;Bounds%20rect%20top&quot;);rename_annotation(&quot;header-anim-b-left&quot;,&quot;Bounds%20rect%20left&quot;);rename_annotation(&quot;header-anim-b-bottom&quot;,&quot;Bounds%20rect%20bottom&quot;);rename_annotation(&quot;header-anim-b-right&quot;,&quot;Bounds%20rect%20right&quot;)\">annotate<\/a>).<\/p>\n<p>Let find out how to find the other sprite frames and maybe it will become clear. Using the <code>0x250<\/code> length (at offset 8 in the header) and adding it to <code>0xc<\/code> (I am able to do <em>that<\/em> addition in my head) we can check what&#8217;s past the first sprite:<\/p>\n<figure id=\"header-anim-2\">\n<pre><code class=\"language-annotated\">[00000258]: [00][000000]  .... =&gt; #line-258, Picture command terminator #+command-null, Zeros #+command-null\n\n[0000025c]: [0000][0000]  .... =&gt; #line, Bounds rect top #b-top, Bounds rect left #b-left,\n[00000260]: [0018][0018]  .... =&gt; #line, Bounds rect bottom #b-bottom, Bounds rect right #b-right\n[00000264]: [0000022c]  ...,   =&gt; #line, Frame length #length\n[00000268]: [0000][012c]  ..., =&gt; Offset the frame length is based from #offset, Destination rect top #d-top, Destination rect left #d-left\n[0000026c]: [0018][0144]  ...D =&gt; #line, Destination rect bottom #d-bottom, Destination rect right #d-right\n<\/code><\/pre>\n<\/figure>\n<p>(The <span class=\"hover\" data-target=\"header-anim-2.line-258.0\"><code>0x258<\/code> line<\/span> is the null termination command of the first sprite, shown here just for context).<\/p>\n<p>Well\u2026 that just looks like another whole header. That seems easy enough. (<a href=\"javascript:enable_annotation(&quot;header-anim-2-b-top&quot;,&quot;header-anim-2-b-left&quot;,&quot;header-anim-2-b-bottom&quot;,&quot;header-anim-2-b-right&quot;,&quot;header-anim-2-length&quot;,&quot;header-anim-2-offset&quot;,&quot;header-anim-2-d-top&quot;,&quot;header-anim-2-d-left&quot;,&quot;header-anim-2-d-bottom&quot;,&quot;header-anim-2-d-right&quot;)\">annotate<\/a>) But how do we know how big to make our sprite sheet? I guess we could go through every sprite and calculate the max <code>(bottom,left)<\/code> to know how big we need to make our canvas. But\u2026 I don&#8217;t really want to do that, it seems annoying. Then I had a thought, &#8220;what if I just <em>ignore<\/em> their &#8216;destination&#8217; rectangle?&#8221;<\/p>\n<p>After all, we just want to be able to grab one and display it, we don&#8217;t necessarily need to recreate their sprite sheet verbatim. That also makes it really easy. We&#8217;ll pass an extra parameter to our <code>unpack()<\/code> function (the sprite number) and to a loop over all the pictures until we get to the one we want. That&#8217;s super easy because we can just jump to the next one using the length in the header\u2014we don&#8217;t need to parse anything in the actual picture.<\/p>\n<p>Here&#8217;s just the top half of the new <code>unpack()<\/code> function (since it&#8217;s getting big now and the bottom part didn&#8217;t really change):<\/p>\n<pre><code class=\"language-javascript\">function unpack(data, want_frame=0) {\n    data.seek(0);\n    let frame = 0;\n    let width,height,out;\n    while (data.tell() &lt; data.length()) {\n        console.log(\"header\")\n        let bounds_top    = data.read16()\n        let bounds_left   = data.read16()\n        let bounds_bottom = data.read16()\n        let bounds_right  = data.read16()\n        height = bounds_bottom - bounds_top\n        width = bounds_right - bounds_left\n        console.log(`  bounds rect (${bounds_top},${bounds_left},${bounds_bottom},${bounds_right}) ` +\n                                  `(width  ${width}, height ${height})`)\n        let length = data.read32()\n        let next_frame = data.tell() + length;\n        console.log(`  length ${dec_and_hex(length)} (end:${dec_and_hex(next_frame)})`)\n        let dest_top    = data.read16()\n        let dest_left   = data.read16()\n        let dest_bottom = data.read16()\n        let dest_right  = data.read16()\n        console.log(`  dest rect (${dest_top},${dest_left},${dest_bottom},${dest_right})`)\n\n        if (frame != want_frame) {\n            data.seek(next_frame);\n            frame++;\n            continue;\n        }\n\n        out = new Bits(Array(width * height).fill(0))\n[ ... ]\n<\/code><\/pre>\n<p>I also decided to add another text entry for setting which frame to look at (just like all the others you can PgUp and PgDown to increment or decrement), and a popup menu to set which sprite-set to display:<\/p>\n<div class=\"resizer\" style=\"height:30em;\"><iframe src=\"\/ambrosia-decode\/8\/decode.html\" class=\"final\"><\/iframe><\/div>\n<p><i>\\o\/<\/i><\/p>\n<p><em>Now<\/em> I think we&#8217;re done\u2014we&#8217;ve successfully figured out every byte of the file format and can unwrap any sprite and display it on the screen with the correct colors.<\/p>\n<p><script type=\"module\" src=\"\/ambrosia-decode\/annotate.js\"><\/script><\/p>\n<p><script>\n window.addEventListener(\"load\", () => {\n     [...document.getElementsByTagName(\"iframe\")].forEach(iframe=>\n         iframe.contentDocument.getElementsByTagName(\"body\")[0].style.color=\n             window.getComputedStyle(document.body , null).getPropertyValue(\"color\"));\n })\n<\/script><\/p>\n","protected":false},"excerpt":{"rendered":"<p>My brother nerd sniped me the other day. He wanted to see if he could extract the images out of an old game by Ambrosia Software called &#8220;Slithereens&#8221;. It was released on 1998-12-15 for Mac OS 9. That sounds easy enough\u2026 Digging though resource forks Old Mac OS had the ability to have a 2nd &hellip; <a href=\"https:\/\/porkrind.org\/missives\/decoding-the-sprite-format-of-a-25-year-old-game\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Decoding the sprite format of a 25 year old game<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[3],"tags":[13,19],"class_list":["post-744","post","type-post","status-publish","format-standard","hentry","category-software","tag-javascript","tag-reverse-engineering","ambrosia-decode"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/porkrind.org\/missives\/wp-json\/wp\/v2\/posts\/744","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/porkrind.org\/missives\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/porkrind.org\/missives\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/porkrind.org\/missives\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/porkrind.org\/missives\/wp-json\/wp\/v2\/comments?post=744"}],"version-history":[{"count":137,"href":"https:\/\/porkrind.org\/missives\/wp-json\/wp\/v2\/posts\/744\/revisions"}],"predecessor-version":[{"id":898,"href":"https:\/\/porkrind.org\/missives\/wp-json\/wp\/v2\/posts\/744\/revisions\/898"}],"wp:attachment":[{"href":"https:\/\/porkrind.org\/missives\/wp-json\/wp\/v2\/media?parent=744"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/porkrind.org\/missives\/wp-json\/wp\/v2\/categories?post=744"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/porkrind.org\/missives\/wp-json\/wp\/v2\/tags?post=744"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}