Comment fonctionne ce JavaScript obscurci?

93

Comment fonctionne le JavaScript suivant?

Je comprends que c'est du code minifié. J'ai essayé de le désobscurcir un peu, mais je ne parviens pas à comprendre clairement comment il produit cet effet. Je peux voir qu'il utilise des chaînes pour une itération quelconque, l'utilisation de l'objet Date, une manipulation de chaîne étrange, des fonctions mathématiques, puis le code s'imprime.

Comment le même effet pourrait-il être réécrit avec un exemple minimal?

eval(z='p="<"+"pre>"/* ,.oq#+     ,._, */;for(y in n="zw24l6k\
4e3t4jnt4qj24xh2 x/* =<,m#F^    A W###q. */42kty24wrt413n243n\
9h243pdxt41csb yz/* #K       q##H######Am */43iyb6k43pk7243nm\
r24".split(4)){/* dP      cpq#q##########b, */for(a in t=pars\
eInt(n[y],36)+/*         p##@###YG=[#######y */(e=x=r=[]))for\
(r=!r,i=0;t[a/*         d#qg `*PWo##q#######D */]>i;i+=.05)wi\
th(Math)x-= /*        aem1k.com Q###KWR#### W[ */.05,0>cos(o=\
new Date/1e3/*      .Q#########Md#.###OP  A@ , */+x/PI)&&(e[~\
~(32*sin(o)*/* ,    (W#####Xx######.P^     T % */sin(.5+y/7))\
+60] =-~ r);/* #y    `^TqW####P###BP           */for(x=0;122>\
x;)p+="   *#"/* b.        OQ####x#K           */[e[x++]+e[x++\
]]||(S=("eval"/* l         `X#####D  ,       */+"(z=\'"+z.spl\
it(B = "\\\\")./*           G####B" #       */join(B+B).split\
(Q="\'").join(B+Q/*          VQBP`        */)+Q+")//m1k")[x/2\
+61*y-1]).fontcolor/*         TP         */(/\\w/.test(S)&&"#\
03B");document.body.innerHTML=p+=B+"\\n"}setTimeout(z)')//

JSFiddle

Alexandre
la source
8
Une animation cool ... pourrait finir par l'utiliser quelque part en fait!
tymeJV
7
Oh sympa. Je n'ai pas remarqué le violon.
ThiefMaster
37
C'est ce qu'on appelle un Quine, et c'est l'un des Quine les plus fantastiques que j'ai jamais vu. en.wikipedia.org/wiki/Quine_(computing)
David Souther
9
@Roko C. Buljan je pense que c'est sa page: aem1k.com
Alexander
5
On dirait que l'auteur a maintenant mis en place une version annotée sur GitHub.
Der Hochstapler

Réponses:

67

Avant - propos : j'ai beaucoup embelli et annoté le code sur http://jsfiddle.net/WZXYr/2/

Considérez la couche la plus externe:

eval(z = '...');

Une chaîne de code est stockée dans la variable z. L'opérateur d'affectation renvoie la valeur affectée, de sorte que la chaîne de code est également transmise en tant qu'argument dans eval.

La chaîne de code zs'exécute à l'intérieur de eval. Le code est extrêmement obtus, même lorsqu'il est nettoyé, mais il semble:

  1. Analyser une chaîne de nombres en base 36, délimitée par le caractère 4.
  2. Peupler une carte des valeurs, en utilisant les variables globales e, xety de tenir l' état de la carte. L'état de la carte est en partie fonction de la seconde actuelle sur l'horloge murale ( new Date / 1e3).
  3. En utilisant les valeurs de la carte, le code génère une chaîne de sortie, p
    • le code utilise p += " *#"[index]pour décider s'il faut utiliser un espace, un astérisque ou une marque de hachage, où indexest réellement e[x++] + e[x++](comme indiqué ci-dessus, eetx sont responsables de l'état de la carte)
    • si l'index est plus grand que la longueur de " *#", il existe un code de secours qui remplit la sortie pavec les caractères de z. Les personnages internes sont remplis de personnages d'animation, tandis que les caractères externes sont extraits z.

À la fin du code, il y a un appel à setTimeout(z), qui évalue de manière asynchrone la chaîne de codez . Cet appel répété de zpermet au code de faire une boucle.

Exemple simple:

Voici une version super simple ( http://jsfiddle.net/5QXn8/ ):

eval(z='p="<"+"pre>";for(i=0;i<172;++i)if(i > 62 && i < 67)p+="!---"[~~(new Date/1e2 + i)%4];else p += ("eval(z=\'" + z + "\')")[i];document.body.innerHTML = p;setTimeout(z)')
  1. La forboucle ajoute chaque caractère à la chaîne de sortie p(la chaîne est longue de 172 caractères):

    for(i=0;i<172;++i)
  2. Le conditionnel interne décide si nous sommes sur un personnage entre la position 62 à 67, qui sont les personnages animés:

    if(i > 62 && i < 67)
  3. Si c'est le cas, imprimez !---, décalé en fonction du dixième de la deuxième valeur d'horloge murale. Cela fournit l'effet d'animation.

    p+="!---"[~~(new Date/1e2 + i)%4]

    (Toute la méchanceté autour new Dateest vraiment là pour transformer une valeur de date en un nombre compris entre 0 et 3.)

  4. Sinon, si nous ne sommes pas sur un caractère animé, alors imprimez le icaractère d' index à partir de la chaîne définie par

    "eval(z='" + z + "')"

    Autrement dit, la chaîne de code zentourée par eval('et ').

  5. Enfin, sortez la chaîne et utilisez setTimeoutpour mettre en file d'attente une autre exécution de z:

    document.body.innerHTML = p;setTimeout(z)

Notez que ma sortie finale n'est pas tout à fait correcte - je n'ai pas pris en compte les contre-obliques vers la fin - mais cela devrait tout de même vous donner une assez bonne idée du fonctionnement général de la technique.

apsillers
la source
8
Notez cette github.com/aemkei/world/blob/master/annotated.js - la propre version annotée de l'auteur dans GitHub.
Benjamin Gruenbaum
36

Voici la source annotée. Ps: je suis l'auteur;)

function z(){                     // will be replaced with eval

  p = "<" + "pre>";               // use <pre> tag for formatted output

  for (                           // loop though lines
    y in n = (                    // y - the line number
      "zw24"      +               // n - the encoded data
      "l6k4"      +               // every line holds encoded data
      "e3t4"      +
      "jnt4"      +               // string will be concated in build process
      "qj24"      +
      "xh2  4"    +               // data after spaces will be ignored but
      "2kty24"    +               // … is used to not break block comments
      "wrt4"      +               // … which will save some chars
      "13n24"     +
      "3n9h24"    +
      "3pdxt4"    +
      "1csb   4"  +
      "3iyb6k4"   +
      "3pk724"    +
      "3nmr24"
    ).split(4)                    // data will be split by (unused) 4

  ){
    for (                         // loop throug every char in line
      a in t = parseInt(          // numbers are encoded as string
        n[y],                     // … with a base of 36
        36
      ) + (                       // large number will be converted to string
        e =                       // e - holds the rendered globe
        x =                       // x - horizonal position
        r = []                    // r - bitmap flag if pixel is set
      )
    ){
      r = !r;                     // toggle binary flag

      for (                       // look though bitmap states
        i = 0;                 
        t[a] > i;                 // draw pixel t[a]-times
        i += .05
      )
        with (Math)               // refer to Math later
          x -= .05,
          0 > cos(                // prevent backface visibility
            o =
              new Date / 1e3 +    // get rotation based on current time
              x / PI
          ) && (
            e[                    // access matrix
              ~~(                 // convert float to integer
                sin(o) *          // rotate around y axis
                sin(.5 + y/7) *
                32                // scale up the globe
              ) + 60              // move to center
            ] = -~r               // store bitmap state in render matrix
          )
    }

    for (                         // loop through columns
      x = 0;
      122 > x;                    // break after char 122
    ) p += "   *#"[               // add space, asterisk or hash
        e[x++] +                  // … based pixel opacity
        e[x++]
      ] || (S = (                 // otherwise use the original code
        "eval(z='" +              // inception of missing "eval" statement
          z
            .split(B = "\\")      // escape \ with \\
            .join(B + B)

            .split(Q = "'")       // escape ' with \'
            .join(B + Q) +

          Q +                     // add missing ')

          ")////////"             // add extra chars to fill mapping
        )[
          x / 2 +                 // get character at current position
          61 * y-1
        ]

      ).fontcolor(                // colorize outpu
        /\w/.test(S) &&           // test for [0-9A-Z]
        "#03B"                    // render blue
                                  // otherwise pink (default)
      );

    document.body.innerHTML =     // render output
      p +=                        // append new line
      B +                         // add backspace
      "\n";                       // add new line
  }

  setTimeout(z)                   // render animation on next frame
}
z()
aemkei
la source
5
Remarque, il est également expliqué dans cette vidéo youtube.com/watch?v=RTxtiLp1C8Y
Benjamin Gruenbaum
21

Voici une autre version désobfusquée manuellement, déplaçant toute initialisation de l'expression dans ses propres instructions:

z='p="<"+"pre>"/* ,.oq#+     ,._, */;for(y in n="zw24l6k\
4e3t4jnt4qj24xh2 x/* =<,m#F^    A W###q. */42kty24wrt413n243n\
9h243pdxt41csb yz/* #K       q##H######Am */43iyb6k43pk7243nm\
r24".split(4)){/* dP      cpq#q##########b, */for(a in t=pars\
eInt(n[y],36)+/*         p##@###YG=[#######y */(e=x=r=[]))for\
(r=!r,i=0;t[a/*         d#qg `*PWo##q#######D */]>i;i+=.05)wi\
th(Math)x-= /*        aem1k.com Q###KWR#### W[ */.05,0>cos(o=\
new Date/1e3/*      .Q#########Md#.###OP  A@ , */+x/PI)&&(e[~\
~(32*sin(o)*/* ,    (W#####Xx######.P^     T % */sin(.5+y/7))\
+60] =-~ r);/* #y    `^TqW####P###BP           */for(x=0;122>\
x;)p+="   *#"/* b.        OQ####x#K           */[e[x++]+e[x++\
]]||(S=("eval"/* l         `X#####D  ,       */+"(z=\'"+z.spl\
it(B = "\\\\")./*           G####B" #       */join(B+B).split\
(Q="\'").join(B+Q/*          VQBP`        */)+Q+")//m1k")[x/2\
+61*y-1]).fontcolor/*         TP         */(/\\w/.test(S)&&"#\
03B");document.body.innerHTML=p+=B+"\\n"}setTimeout(z)';

p = "<" + "pre>";
n = ["zw2", "l6k", "e3t", "jnt", "qj2", "xh2 x/* =<,m#F^    A W###q. */", "2kty2", "wrt", "13n2", "3n9h2", "3pdxt", "1csb yz/* #K       q##H######Am */", "3iyb6k", "3pk72", "3nmr2", ""]
for (y in n) {
    e = [];
    x = 0;
    r = true;
    t = parseInt(n[y], 36) + "";
    for (a in t) {
        r = !r
        for (i = 0; i < t[a]; i += 0.05) {
             x -= 0.05;
             o = new Date / 1e3 + x / Math.PI
             if (Math.cos(o) < 0)
                 e[~~(32 * Math.sin(o) * Math.sin(0.5 + y / 7)) + 60] = -~r;
        }
    for (x = 0; x < 122;) {
        S = "eval" + "(z='" + z.split(B = "\\").join(B + B).split(Q = "'").join(B + Q) + Q + ")//m1k"
        p += "   *#"[e[x++] + e[x++]] || S[x/2+61*y-1]).fontcolor(/\w/.test(S[x/2+61*y-1]) && "#03B");
    }
    p += B + "\n";
    document.body.innerHTML = p;
}
setTimeout(z)

Voici ce qui se passe:

  • zest une chaîne multiligne contenant tout le code. C'est evaléd.
  • À la fin du code, zest passé à setTimeout. Cela fonctionne comme requestAnimationFrameeteval ensemble, en l'évaluant dans un intervalle au taux le plus élevé possible.
  • Le code lui-même s'initialise p, le tampon de chaîne auquel le HTML sera ajouté et nun tableau de nombres encodés en base 36 (joints en une chaîne par "4", les commentaires étant des déchets non pertinents qui ne sont pas pris en compte par parseInt).
  • chaque nombre dans nencode une ligne ( n.length == 16). Il est maintenant énuméré .
  • Un tas de variables est initialisé, certaines déguisées en elittéral de tableau mais elles sont ensuite converties en nombres ( x) ou booléens ( r) ou en chaînes ( t) lorsqu'ils sont utilisés.
  • Chaque chiffre du nombre test énuméré, inversant le booléen à rchaque tour. Pour différents angles x, et en fonction de l' heure actuelle new Date / 1000 (de sorte qu'il donne une animation), le tableau eest rempli à l'aide de quelques opérateurs au niveau du bit - avec 1quand rest faux et 2s quand rest vrai à ce moment.
  • Ensuite, une boucle parcourt les 61 colonnes de l'image, de x=0à 122 par étapes doubles, en ajoutant des caractères simples à p.
  • Bétant la barre oblique inverse, la chaîne Sest construite à partir de la chaîne de code zen échappant les barres obliques inverses et les apostrophes, pour obtenir une représentation précise de ce à quoi elle ressemblait dans la source.
  • Tous les deux nombres consécutifs de esont ajoutés et utilisés pour accéder à un personnage de " *#", pour construire l'image animée. Si l'un des indices n'est pas défini, l' NaNindex se résout en un caractère indéfini et à la place le caractère respectif de la Schaîne est pris (consultez la formule x/2+61*y-1). Si ce caractère doit être un caractère de mot , il est coloré différemment à l'aide de la fontcolorméthode String .
  • Après chaque ligne, le retour arrière et un saut de ligne sont ajoutés pet la chaîne HTML est affectée au corps du document.

Comment le même effet pourrait-il être réécrit pour un exemple minimal?

Voici un autre exemple:

setInterval(z='s=("setInterval(z=\'"+\
z.replace(/[\\\\\']/g,"\\\\$&")+"\')"\
).match(/.{1,37}/g).join("\\\\\\n");d\
ocument.body.innerHTML=\"<\\pre>"+s.s\
lice(0, 175)+String( + new Date()).fo\
ntcolor("red")+s.slice(188)')

( démo sur jsfiddle.net )

Il contient tout ce dont vous avez besoin pour ce type d'animation:

  • setIntervalet Datepour l'animation
  • Une reconstruction de son propre code (à la quine ), ici:

    s = ( "setInterval(z='" // the outer invokation
          + z.replace(/[\\\']/g,"\\$&") // the escaped version
        + "\')" ) // the end of the assignment
        .match(/.{1,37}/g).join("\\\n"); // chunked into lines
  • La sortie via document.body.innerHTMLet un <pre>élément

  • Remplacement de certaines parties du code par la chaîne animée
Bergi
la source
2
je dois admettre, excellente réponse!
rafaelcastrocouto
5

Une chaîne avec tout le code est évaluée et un délai d'expiration fait la boucle; La chaîne est stockée dans une variable nommée zet au milieu du code, entre les commentaires /*et */il y a un "Earth ASCII Art". Le code analyse les commentaires et modifie le contenu du document, en conservant les js et en mettant à jour l'art. Ci-dessous est juste le code tranché:

  p="<pre>";
  for(y in n="zw24l6k4e3t4jnt4qj24xh2 x42kty24wrt413n243n9h243pdxt41csb yz43iyb6k43pk7243nmr24".split(4)){ 
    for(a in t = parseInt(n[y],36)+(e=x=r=[]))
      for(r=!r,i=0;t[a]>i;i+=.05)
        with(Math) x-= .05,0>cos(o=new Date/1e3+x/PI)&&(e[~~(32*sin(o)*sin(.5+y/7))+60] =-~ r);
          for(x=0;122>x;) p += "   *#"[e[x++]+e[x++\]] ||
              (S=("eval"+"(z=\'"+z.split(B = "\\\\").join(B+B).split(Q="\'").join(B+Q)+Q+")//m1k")[x/2+61*y-1]).fontcolor(/\\w/.test(S)&&"#\03B");
    p += B+"\\n"
    document.body.innerHTML= p
  }
rafaelcastrocouto
la source
6
Quoi qu'il en soit, incroyable comment autour de l'équateur le graphique a plus d'effets ... incroyable. +1 BTW
Roko C. Buljan