Created
July 3, 2012 11:41
-
-
Save tlack/3039247 to your computer and use it in GitHub Desktop.
p01's Matraka javascript demo decoded
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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
Ingenious Mathieu.
Respect.