鳥小屋.txt

主に自作ゲームをつくったりしているよ。制作に関することやそうじゃないことのごった煮ブログ

iOS13でAkashicEngine製のゲームがアツマールで動かない場合がある件

3行でおk

  • iOS13のCookie周りに不具合があるっぽい
  • AkashicEngineのアツマール用エクスポートでその不具合を踏み抜く場合がある
  • iOS14では直ってるのでOSを更新してもらうか、ゲーム作者が回避策を入れる必要がある

どういうわけか:画像が読み込めない

僕がゲームアツマールに投稿している AkashicEngine 製のゲームについて、ときどき「ゲームをしようとしても真っ黒の画面のまま進まない」というバグ報告がありました。
いろいろ調べてみると、どうも画像ファイルの読み込みがコケてしまうようで、読み込みエラーで止まってしまうようでした。

これは scene.assetLoadFailed(~) を使って、とりあえずエラーメッセージだけ表示してみた図。

このときはとりあえずエラーメッセージだけ出して放置していたのですが、他のゲームでも同様の報告をもらったので詳しく調べてみたところ、どうやら「画像読み込み時にCookieがついておらず、アツマールの認証に弾かれている」ようだということがわかりました。

画像読み込みとCookie

untainted: true

AkashicEngineの中身(正確には akashic-cli の中身)を見てみると、アツマール用のエクスポート時には game.json の ImageAsset に untainted: true というものがつくようになっています。
この実装が入った経緯を追っかけてみると、どうもニコ生上で動くために必要なパラメータだそう。

この untainted: true が付くと何が起きるのかというと、AkashicEngine内で画像ファイルを読み込む際に crossOrigin = 'anonymous' がくっつきます。

// 該当部分のコードを抜粋
const image = new Image();

if (this.hint && this.hint.untainted) {
    image.crossOrigin = "anonymous";
}

あれ、crossOrigin="anonymous" してCookie大丈夫なんだっけ……?と思って調べてみたところ、同一オリジンであればCookieなども付くし、そうでなければ付かないという動きをするそう。Cookie使わない用途でしか使ったことなかったから知らなかった……

……が、iOS13ではこれはうまく動きません(◞‸◟)

デモ:Cookie見えるかチェック

以下の検証用のデモページを作成しました。

  • https:// ru-cookie-demo-202105.herokuapp.com/
    • Herokuの無料プランが無くなったため見れなくなりました(◞‸◟)

ページ内にある「Cookieの内容を更新する」というリンクを押すと、Cookieに現在のサーバー時刻が保存されます。
そして、ページ内にはCookie内のデータがテキストで表示されるほか、JavaScriptで画像を2枚取得してページ内に設置されます。

こんな感じ(サーバ時刻がJSTじゃないのは許してね)

グレーのやつ、画像です。サーバーでCookieの内容を画像に書き込んで出力しています。
1枚目は crossOrigin = 'anonymous' がついていて、2枚目はついていないものです。

  function load(flag) {
    var img = new Image();
    if (flag) img.crossOrigin = 'anonymous';
    img.src = './test.png?a=' + Date.now();
    img.onload = () => {
      var dom = document.querySelector(flag ? '#a' : '#b');
      dom.append(img);
    };
    img.onerror = () => {
      alert('error');
    };
  }
  load(false);
  load(true);

当然、先ほどのページにかかれているとおり、今回は同一オリジン上なので crossOrigin = 'anonymous' がついていようが、ついていまいがCookieが送信され、両方の画像のなかにCookieの文字列が書き込まれています。

一方その頃iOS13

ウオオオオオオオオオオオ!!!!!!

そんなわけでダメです。ぼくのiPhone 11+iOS13では動きません(◞‸◟)
crossOrigin = 'anonymous' の指定があると、たとえ同一オリジンであろうともCookieがつかなくなります。

ちなみに iPhone7+iOS14.5 で同じページを開いてみたところ、こちらは問題なく動きます。
おそらくiOS14では正常に動作するようになっているもよう。

つまり何が起きているのか

  1. AkashicEngineのアツマール用エクスポートでは untainted: true という設定が付く
    • ニコ生ゲームに必要だそう
  2. untainted: true がつくと、画像読み込み時に crossOrigin = 'anonymous' が指定される
  3. iOS13は crossOrigin = 'anonymous' が設定されると同一オリジンでもCookieが送られない
    • 本来は送られる必要があるので、これはiOS13の不具合
    • iOS14では既に修正済みっぽい
  4. アツマールではゲームデータの保護にCookieが使われているため、iOS13でAkashicEngine製のゲームをプレイすると、画像ファイルの読み込みができない場合がある

「場合がある」というふわっとした書き方をしているのは、動く場合もあるからです>< なんでや。

で、結局どうすればいいの?

解決策1:遊ぶ人にiOSのアップデートをしてもらう

普通にiOSのバグなので、iOSのアップデートをしてもらえば解決です。
「iOS14以上対応です」とか書いておけばいいんじゃないかな。

解決策2:クソみたいな回避策を埋め込む

AkashicEngineのアツマール用エクスポートは出力される index.html の中に色々なデータが書き込まれるという特徴(?)があります。理由は知らない。
その性質を利用して「ニコ生ゲームでだけ untainted: true して、アツマールでは untainted: true しない」ということができます。

akashic-cli での出力時に --inject ファイル名 というオプションをつけることで、 index.html 内に任意の要素を追加することができます。
そのため、以下のようなコードを index.html に追加します。

<script>
  // iOS bug fix
  if (window.gLocalAssetContainer && window.gLocalAssetContainer['game.json']) {
    window.gLocalAssetContainer['game.json'] = window.gLocalAssetContainer['game.json'].replace(
      /untainted%22: true/g,
      'untainted%22: false'
    );
  }
</script>

見た瞬間「うわぁ」って言いたくなりますね!
window.gLocalAssetContainer['game.json'] にアツマール用の game.json の中身が入っているので、ゲーム実行前に untainted: trueuntainted: false に書き換えています><

これによって無理やりアツマールでだけ、untainted: false にすることができますが、AkashicEngineの仕様が変わったら即死するような気もするのでやる場合は自己責任で……

まとめ

たのむぞApple!