ハクソク

世界を動かす技術を、日本語で。

このゲームは、Windows、Linux、そしてブラウザで動作する13 KiBの単一ファイルです。

概要

Justine Tunneycosmopolitan libcプロジェクトに触発され、GUI非対応バイナリ肥大化の課題を踏まえて、16KiB未満のマルチプラットフォームゲーム制作に挑戦。
SnakeゲームWindows、Linux、ブラウザで単一ソースから動作するよう実装。
各プラットフォームごとの工夫バイナリ圧縮技術を駆使し、最終ファイルサイズ13,312バイトを実現。
ゲーム内容や操作方法レベル進行実装技術詳細を解説。
ファイル構造や起動メカニズムも詳述。

マルチプラットフォーム対応Snakeゲーム制作記

  • cosmopolitan libcはCソースを複数OS対応の単一バイナリにできるツールキット
  • GUI未対応バイナリ肥大の課題を背景に、独自アプローチで小型ゲーム制作に挑戦
  • 目標16KiB未満SnakeゲームWindows、Linux、ブラウザで動作させること
  • 単一ソースファイルから全環境向けにビルド・実行可能な設計

ゲーム内容・操作

  • クラシックなSnakeゲームルールを全プラットフォーム共通で実装
  • 操作方法矢印キーまたはWASDキーで移動、ESCで終了(対応プラットフォームのみ)、Rでリセット、Pで一時停止、スペースで開始
  • スコア管理:食べ物1つで10点15%確率で出現する黄色フルーツ20点
  • フルーツの出現・消滅:一定間隔で出現し、食べられなければ一定時間で消滅
    • 消滅タイマーはスネークの長さに比例
  • 10個のフルーツを食べると次レベル進行、壁配置がランダム化
    • 迷路生成:常にスネークの頭からフルーツまでの経路が存在
    • 初期配置もランダム、進行方向に最低5マスの空きスペースを保証

各プラットフォーム向け実装

  • Windows版:WinAPIを使いi686 Visual C向けC言語で実装
    • 圧縮スクリプトデコンプレッサスタブを付与
    • PEヘッダの余白を利用し、シェルスクリプトを埋め込む
    • Windowsのapphelp機構に依存し、初回起動時にエラー表示される場合あり(再実行で解消)
  • Linux版x86_64 Linux向けにClangとX11でC言語実装
    • lzma圧縮小型シェルスクリプトドロッパでバイナリ展開・実行
  • HTML5版JavaScriptHTML5 Canvasで実装
    • ファイル先頭のガーベジデータを無視し、CSSで不可視化して本体レンダリング

ファイル構造と起動メカニズム

  • 3つの実装バイナリを連結し、各プラットフォームが自身に対応する部分のみ実行
    • Windows:PEヘッダ認識でWindowsコード実行
    • Linux:シェルスクリプトでバイナリ抽出・展開・実行
    • ブラウザ:HTMLタグまでスキップし、通常のWebページとして解釈
  • 最終ファイルサイズ13,312バイト
  • コード抜粋バイナリレイアウトも詳細に分析

技術的工夫

  • PEファイルシェルスクリプトの共存によるポリグロット設計
  • LZMA圧縮シェルドロッパでLinuxバイナリの小型化・展開
  • HTMLとCSSの悪用で不要データの非表示化
  • 全てのバイナリを1ファイルにまとめることで、クロスプラットフォーム性と小型化を両立

まとめ

  • cosmopolitan libcの課題を踏まえた独自アプローチ
  • マルチプラットフォームで動作する極小ゲームの実現
  • 圧縮・埋め込み技術バイナリフォーマットの知識を活かした設計
  • C言語とJavaScriptによる3種実装単一ファイル配布の工夫
  • 技術的チャレンジバイナリ解析に興味のある開発者向けの事例

Hackerたちの意見

自分の場合: - ブラウザ: .htmlに名前を変えたら動いた - Linux: "./snake.com: line 20: lzma: command not found"。xzパッケージをインストールしたら動いた(XWaylandは有効にしてたからX11は動いたけど、厳しいWaylandセッションだと必要かも)。 - Windows: .comでも.exeに名前を変えても「アプリケーションが正しく開始できませんでした (0xc0000005)。OKをクリックしてアプリケーションを閉じてください。」って出る。これをどうやって動かすか分からないけど、AV関連ではないことは確か(このサンドボックスVMではそれを削除してる)。編集: 今は3つとも動くようになった。Windowsでは、以前にいくつかのアプリをテストするために全プログラムでDEPを有効にしてたから、それをオフにしたら起動できた。
投稿に書いてあるよ。
$ chmod +x snake.com $ ./snake.com だけで実行すると、Monoを使おうとして「アセンブリ './snake.com' を開けません: ファイルに有効なCILイメージが含まれていません。」ってなる。でも、Bashで明示的に実行すると動く: $ bash snake.com かなり便利だけど、どのLinuxでもすぐには動かないね、少なくとも:p Debian 13で試してる。
Windows 11では動くよ。
Polyglotの面白いところは、誰もそれをもっと早くやらなかったことだね。10年か20年前には実現可能だったはず。
そういえば、最初のポリグロットファイルがいつ公開されたのか気になるな。ずっと前からあったと思ってたけど。EICAR.COMがCOM/plaintextポリグロットとして思い浮かぶね。
クロスプラットフォームではないけど、Windows用のkkriegerゲームを思い出す。96kのFPSゲームで、当時は見た目がすごく印象的だった。 https://web.archive.org/web/20100304155706/http://www.thepro...
あのリンクから: 現代のPCでもまだ動く!ダウンロードして起動できた。
メトロイドプライム4をプレイしてるときにこれを思い出した。これと同じアイデアで、どんな現代のゲームが作れるんだろう?
関連する話だけど、エミュレーターでオリジナルのゼルダゲームのファイルサイズが小さいのを見ると、どれだけ少ないテキストやコード、情報でこんなに素晴らしい体験ができたのか、どれほどの影響があったのか、そして自分にどれだけの時間の魅了をもたらしたのかに驚かされる。 https://en.wikipedia.org/wiki/The_Legend_of_Zelda_(video_gam...
ゼルダの伝説1は128kBだったよ、圧縮なしでね。続編はその倍だね。
僕が一番好きだったトリックは、すべてのダンジョンや洞窟が一つの長方形のマップの一部だったこと。デザイナーたちは特定のデザインを彫り込んで、他のレベルは残りのマップ画面に合わせて作られてたから、スペースにぴったり収まってたんだ。洞窟は単画面の隙間を埋めるために追加されてた。 https://ian-albert.com/games/legend_of_zelda_maps/
確かに、魔法だね。コモドール64についてはどう?64KBの中に巨大なマップがあるゲーム(Eindeloos, Radarsoft, 1985)があったよ。最近、誰かがそのマップ(500画面)を抽出したんだけど、PNGだけで800KBもあるんだ。ストーリーを見て、ズームインしてマップの中の小さなハートを探してみて! https://adayinthelifeof.nl/2025/03/07/endless.html
https://js13kgames.com/
一つのファイルでどこでも動かせるアプリケーションのアイデアが大好きなんだ。サーバーレスプラットフォームでこれを実現しようと頑張ってるよ。複雑なデータ駆動型アプリをたった一つの.htmlファイルと、主に宣言的なHTMLマークアップだけで作れるんだ(リモートサーバーから読み込まれるウェブコンポーネントのおかげでね)。現代のブラウザ機能を使えば、バンドルシステムは必要ない。これを取り除くと、全く新しい世界が広がるよ。file://プロトコルを使って.htmlファイルを読み込む能力は、強力で、しばしば見落とされがちな機能なんだ。実際には、HTMLファイルをダブルクリックするだけで、ブラウザでアプリがすぐに動くってことだね。
残念ながら、多くのブラウザ機能は非HTTPSの環境では使えないんだよね。
あなたのシングルファイルHTMLアプリの開発はどんな感じで進めてるの?公開されてる例とかある?すごく興味深いね。
関連する話だけど、WindowsのEXEファイルはDOSで実行できる(少なくともDOSが存在していた頃、つまりWindows 3.1xや9xの時代ね)。でも大抵の場合、DOSの部分は「このプログラムはMicrosoft Windowsを必要とします。」って表示して終了しちゃう。例外としてregedit.exeがあって、これを使うとDOSでもレジストリの値をインポートできるんだ。(あれ、でもどうやってWindows APIを使わずにそれができるんだろう?)
> 例外としてregedit.exeがある これってどこかで変わったかもしれないね。君の質問の後半部分、Windows APIを使わずにどうやって変更を加えたのか気になってたんだけど(古いDOS APIを使ってると思ってた)、僕の`regedit.exe`には「このプログラムはDOSモードでは実行できません。」っていうDOSスタブが入ってるよ。
> へぇ、でも、どうやってWindows APIを使わずにそれができるんだろう?何も知らないけど、彼らがWindows APIが使ってるコードを直接インポートするか、実装コードの場所を知ってそれを読み込むか、ライブラリを静的にリンクしてるんじゃないかな!結局、regeditは他の非公式プログラムが守らなきゃいけないクリーンネスルールに従う必要がないからね。多分、レジストリエディティングAPIやフォーマットが変わったら、regeditもそれに合わせて更新されるから!
Linuxの実行ファイルを抽出したら、正しく読み込んで実行できるのに、readelfやobjdumpがそれに対してエラーを出すのに驚いたよ。調査してみたら、動的リンカーの名前がPT_DYNAMICヘッダーエントリの「未使用」フィールドに押し込まれていて、スペースを節約していることが分かったんだ。プログラムヘッダー: タイプ オフセット VirtAddr PhysAddr ファイルサイズ メモリサイズ フラグ アライン INTERP 0x0000000000000088 0x0000000000010088 0x0000000000010088 0x000000000000001c 0x000000000000001c 0x0 [要求されたプログラムインタープリタ: /lib64/ld-linux-x86-64.so.2] DYNAMIC 0x00000000000000e0 0x00000000000100e0 0x6c2f343662696c2f 2つの質問があるんだけど、1. これは手動でやったの?それともこれを行うツールを使ってるの?他にもサイズ削減のトリックがいくつか見えるよ。2. こんなバイナリに対しても動作する実行ファイルを調べるツールって誰か知ってる?
どうやってチョークするの?ここではどっちも問題なく動いてるよ。
ndisasmを使えば読めるし、16進数エディタも使えるよ。こんな形式を壊すツールなんて必要ないし、無駄な保存で価値もない。AVや他のものに問題を引き起こす可能性もあるし。WindowsのDEPについてのコメントも見たけど、正直言って、こんなものには10フィートの棒でも触りたくないわ。もし作成者がみんなに遊んでもらいたいなら、普通のバイナリを提供すればいいのに。こんな難解なゴチャゴチャじゃなくてさ。
こういうメカニズムが「強化」されて、本当の「ユニバーサル」バイナリを作ることができるのかな?Haxeみたいなソースからソースにコンパイルする言語を使って。Haxeなら、一度アプリケーションを書くだけで、win32とlinuxの両方をC++にコンパイルしてターゲットにできるし(その後、各プラットフォーム用のツールでコンパイルする)、さらにHTMLをJavaScriptにコンパイルしてターゲットにできる。記事で説明されてるような同じ連結メカニズムとヘッダーの悪用を使えば、3つのターゲットを一つのファイルにまとめて、すべてのプラットフォームで実行できるようになるんだ!
一番面白いのは、そのものが3つの独立したグラフィックスベースのゲームの実装をつなぎ合わせて、巧妙なマルチプラットフォームローダーに包まれてることなんだ。それでいて、たったの13KiBしか使わないんだから。