あの名曲 shell32.dll を自分で作る (バイナリにwavヘッダを付ける)

名曲です

www.youtube.com

サンプリングレートなどは動画中にあるので、自分のPCがWindowsであれば、同じDLLがあるのでwavファイルが作れるのでは?
WSLならシェル芸もできる!やったね。

やりかた

シェル芸でゴリッとする。

$ cat shell32.dll | xxd -ps | tr -d \\n | awk '{printf("echo %08x | fold -w2 | tac\n", length/2 + 8 + 4 + 2 + 2 + 4 + 4 + 2 + 2 + 4 + 4); printf("echo %08x | fold -w2 | tac\n", length/2); print "echo "$0}' | sed '2iecho -n data | xxd -ps' | sed '2iprintf %04x 16 | fold -w2 | tac' | sed '2iprintf %04x $((16/8*2)) | fold -w2 | tac' | sed '2iprintf %08x $((8000*16/8*2)) | fold -w2 | tac' | sed '2iprintf %08x 8000 | fold -w2 | tac' | sed '2iprintf %04x 2 | fold -w2 | tac' | sed '2iprintf %04x 1 | fold -w2 | tac' | sed '2iprintf %08x 16 | fold -w2 | tac' | sed '2iecho -n WAVEfmt\\\ | xxd -ps' | sed '1iecho -n RIFF | xxd -ps' | sh | tr -d \\n | xxd -r -p > out.wav

上記のシェル芸が何をしているのかと言うと、バイナリにヘッダを追加しています。
shに2回投げているので見通しが非常に悪いですが、概ねsedでコマンドを作って、shに渡しています。
詳細な説明は次のワンライナーのあとに解説します。

最初の sh に投げる前までの出力の12行目までは以下になっています(13行目はデータ部で非常に長いため削っています)。

$ cat shell32.dll | xxd -ps | tr -d \\n | awk '{printf("echo %08x | fold -w2 | tac\n", length/2 + 8 + 4 + 2 + 2 + 4 + 4 + 2 + 2 + 4 + 4); printf("echo %08x | fold -w2 | tac\n", length/2); print "echo "$0}' | sed '2iecho -n data | xxd -ps' | sed '2iprintf %04x 16 | fold -w2 | tac' | sed '2iprintf %04x $((16/8*2)) | fold -w2 | tac' | sed '2iprintf %08x $((8000*16/8*2)) | fold -w2 | tac' | sed '2iprintf %08x 8000 | fold -w2 | tac' | sed '2iprintf %04x 2 | fold -w2 | tac' | sed '2iprintf %04x 1 | fold -w2 | tac' | sed '2iprintf %08x 16 | fold -w2 | tac' | sed '2iecho -n WAVEfmt\\\ | xxd -ps' | sed '1iecho -n RIFF | xxd -ps' | head -n 12
echo -n RIFF | xxd -ps
echo 0145d114 | fold -w2 | tac
echo -n WAVEfmt\ | xxd -ps
printf %08x 16 | fold -w2 | tac
printf %04x 1 | fold -w2 | tac
printf %04x 2 | fold -w2 | tac
printf %08x 8000 | fold -w2 | tac
printf %08x $((8000*16/8*2)) | fold -w2 | tac
printf %04x $((16/8*2)) | fold -w2 | tac
printf %04x 16 | fold -w2 | tac
echo -n data | xxd -ps
echo 0145d0f0 | fold -w2 | tac

それぞれのprintfechosedで追加しています。
なお、データ長が必要な部分はawkを使用しています。

wav ヘッダのフォーマットは、次のページを参考にしました。
wav ファイルフォーマット
ワンライナーでは各フィールドを下から順に追加しているので、shに投げる前は 3フィールド目の'W' 'A' 'V' 'E'部分から順に並んでいます。

fold -w 2 | tacエンディアンの変換を行っています。
sedi コマンドで追加すると各フィールドごとにエンディアン変換ができるのが個人的に便利だと思っています。

結果

聞いてみるとバージョンが違うのか結構違う音が聞こえてきます。。。ただし、共通している部分もありbin -> wavの変換は上手くできているようです。

まとめ

これで好きなバイナリを音楽として聞く事ができますね!

正攻法 *1

$ ffmpeg -f u16le -ac 2 -ar 8000 -i shell32.dll out.wav


整形

最初のワンライナーの整形版です。
はてなブログ上で作ってるので動くかは保証しかねます。雰囲気用です。

RATE=8000 # サンプリングレート
CHANNELS=2 # モノラル=1,ステレオ=2
BIT_DEPTH=16 # ビット深度 (リニアPCM16bitの16で、データの単位のこと)
cat shell32.dll \
  | xxd -ps \
  | tr -d \\n \
  | awk '{
    printf("echo %08x | fold -w2 | tac\n", length/2 + 8 + 4 + 2 + 2 + 4 + 4 + 2 + 2 + 4 + 4);
    printf("echo %08x | fold -w2 | tac\n", length/2);
    print "echo "$0
  }' \
  | sed '2iecho -n data | xxd -ps' \
  | sed '2iprintf %04x '$BIT_DEPTH' | fold -w2 | tac' \
  | sed '2iprintf %04x $(('$BIT_DEPTH'/8*'$CHANNELS')) | fold -w2 | tac' \
  | sed '2iprintf %08x $(('$RATE'*'$BIT_DEPTH'/8*2)) | fold -w2 | tac' \
  | sed '2iprintf %08x '$RATE' | fold -w2 | tac' \
  | sed '2iprintf %04x '$CHANNELS' | fold -w2 | tac' \
  | sed '2iprintf %04x 1 | fold -w2 | tac' \
  | sed '2iprintf %08x 16 | fold -w2 | tac' \
  | sed '2iecho -n WAVEfmt\\\ | xxd -ps' \
  | sed '1iecho -n RIFF | xxd -ps' \
  | sh \
  | tr -d \\n \
  | xxd -r -p > out.wav


改訂履歴

  • 2017/12/27: ワンライナー中で2回 awk を使って、長さを測っていて、遅かったので修正しました。また、整形したワンライナーの追加・全体的な修正をしました。