Skip to content

Instantly share code, notes, and snippets.

@ottonascarella
Forked from tlack/p01-matraka-explained.js
Created August 22, 2012 05:26
Show Gist options
  • Save ottonascarella/3422490 to your computer and use it in GitHub Desktop.
Save ottonascarella/3422490 to your computer and use it in GitHub Desktop.
p01's Matraka javascript demo decoded
// Matraka's source code decoded and beautified
// by @tlack
//
// Matraka is a 1005 byte Javascript "demo" by p01. It includes an 'evolving animation'
// and great dirty synth music. View here:
//
// http://www.p01.org/releases/MATRAKA/matraka.png.html
//
// I fondly recall the demo scene of my youth, puzzling over the work of Future
// Creators and those guys. I was puzzled by this worked so I had to figure it
// out.
//
// First of all, the page seems to manipulate a semi-bug: if the browser gets
// confused by the Content-type of a page, it seems to attempt to interpret it
// as HTML - even if it actually contains image data. In this case, a strange
// mix of behaviors will occur.. the image data will be decoded, but content on
// the page will be evaluated as Javascript as well. Not what I would have
// expected.
//
// Here's the "loader" part of the script, which interprets the decoded image
// data, pulls it out of the screen's canvas, and then eval()s it:
//
// NB. I've reworked this code by hand:
//
// <canvas id=c>
// <img src=# onload="for(
// a=c.getContext( '2d'),i=e='' ,S=String.fromCharCode;
// a.drawImage(this,i--,0),t=a.getImageData(0,0,1,1).data[0];){
// e+=S(t);
// }(1,eval)(e)>
//
// This appears to step through the decoded bytes of the PNG file and then eval
// the result. I don't quite understand the (1,eval) bit though - what does 'for'
// return that's being invoked here?
//
// In Chrome's web console, you can kind of do the same thing:
//
// > for(i=e='';i--;){}(1,eval)
// function eval() { [native code] }
// > for(i=e='';i--;){}(1,eval)('100*3')
// 300
//
// Gotta figure out how this works.
//
// Anyway, so I fetched the page content, removed the 'loader' bits, and then decoded
// the resulting png into a bmp file. bmps are basically raw pixel-by-pixel image data,
// so I knew this would make it easy to decode the rest.
//
// Once I examined that bmp, I saw the javascript. Of course there was a 64 byte header
// that I wrote a quick script to trim off.
//
// This left me with the following (od -c output here, including header):
//
// 0000000 B M " 017 \0 \0 \0 \0 \0 \0 6 \0 \0 \0 ( \0
// 0000020 \0 \0 371 004 \0 \0 001 \0 \0 \0 001 \0 030 \0 \0 \0
// 0000040 \0 \0 354 016 \0 \0 # . \0 \0 # . \0 \0 \0 \0
// 0000060 \0 \0 \0 \0 \0 001 M M M = = = [ [ [ R
// 0000100 R R = = = M M M a a a t t t h h
// 0000120 h . . . r r r a a a n n n d d d
// 0000140 o o o m m m , , , Q Q Q = = = M
// 0000160 M M a a a t t t h h h . . . c c
// 0000200 c o o o s s s , , , c c c . . .
// 0000220 s s s t t t y y y l l l e e e .
// 0000240 . . c c c s s s s s s T T T e e
// 0000260 e x x x t t t = = = ' ' ' p p p
// 0000300 o o o s s s i i i t t t i i i o
// 0000320 o o n n n : : : f f f i i i x x
// 0000340 x e e e d d d ; ; ; t t t o o o
// 0000360 p p p : : : 0 0 0 ; ; ; l l l e
// 0000400 e e f f f t t t : : : 0 0 0 ; ;
// 0000420 ; w w w i i i d d d t t t h h h
// 0000440 : : : 1 1 1 0 0 0 0 0 0 % % % ;
// 0000460 ; ; h h h e e e i i i g g g h h
// 0000500 h t t t : : : 1 1 1 0 0 0 0 0 0
// 0000520 % % % ; ; ; b b b a a a c c c k
//
// You'll note that each pixel in the image is represented by a 3 byte (r,g,b)
// combination. p01 encoded each character of Javascript as a single pixel, or
// 3 bytes. Why didn't they use every byte of the data and make their bootstrap
// code a bit more complicated, but the png data much simpler? I don't know.
// Maybe it compressed much worse? Hard to say. They also left in a lot of
// optional semicolons it seems
//
// It was a simple task to pull out every third byte of the file.
//
// I ran the result through jsbeautifier and did some annotating and the result
// is below. I still don't understand how the real "meat" of it works.
//
M = [
R = Math.random, Q = Math.cos, c.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:#000', e = 'data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEAwF0AAMBdAAABAAgAZGF0YQAA'];
for (t = 2560; t--;) M[t] = {
t: t,
u: t % 48 / 7.64,
v: t / 366.7,
x: 128 - R() * 256,
y: 64 - R() * 256,
z: 128 - R() * 256
};
//
// note, S = String.fromCharCode, assigned in the bootstrapping phase
// btoa() decodes base64 data
//
// what he appears to be doing here is indexing into that odd string to build a decodable
// base64 string
//
for (t = 0; t++ < 8e5;)
e += btoa(
S('13107103135701314204' [(t >> 10 & 15) + (t >> 13 & 4)] * t & 96, R() * 127 * (Math.pow(t / 144000 % 1, 16) / 4 + Math.pow(1 - t / 144000 % 1, 64)), t >> 10 & 7 ^ 5 || R() * 127));
console.log(e);
Z = new Audio(e);
Z.play(setInterval(function b(v, w) {
// note that setInterval calls its callback with no parameters - so what
// are we doing here?
if (v) return w.W - v.W;
// sorting as a way to find the data we need for the next iteration?
M.sort(b);
// here, c refers to the original canvas
h = c.height = 0 | 300 * innerHeight / innerWidth;
// Q = Math.cos
C = Q(r = Z.currentTime / 2);
S = Q(r - 8);
a.rotate((r & 13) / 64 - .1);
B = r / 9;
A = Math.pow(B % 1, 64);
d = [1 - A, 0, A][0 | B++ % 3];
f = [1 - A, 0, A][0 | B++ % 3];
e = [1 - A, 0, A][0 | B++ % 3];
for (t = 2560; t--;) {
v = M[t];
if (v.W > 0) a.fillRect(150 - v.W * v.U, v.W * (v.y + Math.max(16 * Q(r), 150 - r * 42)) + h / 2, v.W * 7, v.W * 7, a.fillStyle = 'hsl(' + [r * 17 - v.y + R() * 48, (16 + R() * 48) + '%', v.W * 7 + 32 - 32 * Q(v.u * 2 - 8) * Q(v.v * 3 - 8)] + '%)');
if (v.t < 2304) Y = 96 - 30 * v.v, D = 32 + 8 * Q(v.u * 2) * Q(v.v * 3), v.x = (96 - 30 * v.u) * d + D * (Q(v.u) * e + Q(v.u) * Q(v.v / 2 - 8) * f), v.z = Y * d + D * (Q(v.u - 8) * e + Q(v.u - 8) * Q(v.v / 2 - 8) * f), v.y = D * d + Y * e + D * Q(v.v / 2) * f;
v.W = 128 / (v.z * C - v.x * S + 96);
v.U = v.x * C + v.z * S
}
a.fillText(['P01 4MAT', 'MATRAKA'][B & 1].substr(a.drawImage(c, 0, r * h % h, 32, h / 8 * (3 + A + Q(B + A)), 0, r * h % h, 300, h / 8 * (3 + A + Q(B + A))), r * 8 - 48) + '|', 32, h / 2)
}, Z.loop = 9))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment