エンジニアなりにポルポテストに合格してみる

エンジニアなりにポルポテストに合格してみる:


:star2: ポルポテストとは?

ポルポテストは、11月25日から大阪文化館・天保山で開催される「荒木飛呂彦原画展 JOJO 冒険の波紋」を記念して実施されるスマホ用Webゲーム。同作に登場するギャング組織・パッショーネの幹部“ポルポ”がジョルノに課した、「24時間ライターの炎を消さなければ合格」という過酷な入団試験を再現したもので、実際に24時間スマートフォンを落ち着けておく必要があります。


687474703a2f2f696d6167652e69746d65646961


試験は端末を垂直に縦持ちしてスタート。ルールとしては、

一定角度以上傾けると失敗

スリープ画面になると失敗

ブラウザ上で「ポルポテスト」ページから別のページに移動したら失敗

「ポルポテスト」を開いているブラウザを閉じて、再度アクセスしても失敗

「ポルポテスト」を開いているブラウザアプリがバックグラウンドにいっても失敗

ブラウザページを更新しても失敗

通信が切れても失敗

サーバが落ちても失敗

受けたプッシュ通知から別の画面を開くと失敗

 

など、条件はかなり厳しめで、事前にチャレンジする日の予定を調節したり、スマートフォンの設定を変えたりしないと難しそうです。
  • 当時は多くの人がチャレンジしていたようですが、「スマホを2台持ってる人しか無理では?」というのが一般的な見解でした
  • もう応募期間も過ぎているので、webエンジニアとして合格できる方法を解説してみようと思います


:pencil: 何が必要?


  • スタンドは使えないので代わりにChromeDevTools(Chromeの検証モード)を使います
  • Safariなどその他のブラウザ搭載のディベロッパーツールでもできるはず
  • つまり、ちょっとフロントを触れる人なら誰でもできちゃいます
  • せっかくなので、できるだけ細かく解説してみます


:bangbang: 問題1:PCからだとテストが受けられない

  • まずはPCのChromeから https://jojo-polpotest.com にアクセスしてみましょう
  • 次のように「このコンテンツはスマートフォンでお楽しみください」と表示されてしまいます


jojo-polpotest.com_pc.html(Laptop with HiDPI screen).png


  • ですが、「右クリック > 検証」などからChromeDevToolを開き、上のタブから適当にiPhoneなどスマホを選択し、画面を更新しなおせばスマホ用のページが表示されます


スクリーンショット 2019-02-04 22.14.13.png


  • これで端末の角度など様々なルールを無視できちゃいます


:thinking: Why?

  • URLをよく見ると、PCの時はhttps://jojo-polpotest.com/pc.htmlに飛ばされていることに気づきます
  • ChromeDevToolsの「Source」を選択し、/js/script.jsを開いてpc.htmlを探してみましょう。


スクリーンショット 2019-02-04 22.19.05.png


  • 「command + F」などで検索してみると、12721行目あたりに次のようなコードが見つかりました
var CheckDevice = function () { 
  function CheckDevice() { 
    // this.checkUaDevaice(); 
 
    var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 
    (0, _classCallCheck3.default)(this, CheckDevice); 
  } 
 
  (0, _createClass3.default)(CheckDevice, [{ 
    key: "checkUaDevaice", 
    value: function checkUaDevaice() { 
      console.log(); 
      var _ua = function (u) { 
        return { 
          Tablet: u.indexOf("windows") != -1 && u.indexOf("touch") != -1 && u.indexOf("tablet pc") == -1 || u.indexOf("ipad") != -1 || u.indexOf("android") != -1 && u.indexOf("mobile") == -1 || u.indexOf("firefox") != -1 && u.indexOf("tablet") != -1 || u.indexOf("kindle") != -1 || u.indexOf("silk") != -1 || u.indexOf("playbook") != -1, 
          Mobile: u.indexOf("windows") != -1 && u.indexOf("phone") != -1 || u.indexOf("iphone") != -1 || u.indexOf("ipod") != -1 || u.indexOf("android") != -1 && u.indexOf("mobile") != -1 || u.indexOf("firefox") != -1 && u.indexOf("mobile") != -1 || u.indexOf("blackberry") != -1 
        }; 
      }(window.navigator.userAgent.toLowerCase()); 
      if (!_ua.Mobile && location.href.indexOf("pc.html") == -1) { 
        location.href = "/pc.html"; 
      } else if (_ua.Mobile && location.href.indexOf("pc.html") !== -1) { 
        location.href = "/"; 
      } 
    } 
  }]); 
  return CheckDevice; 
}(); 
  • どうやらUA(UserAgent)でタブレット・モバイル端末とそれ以外をふるい分けて、PC用ページに飛ばしているようですね
  • さらに、このcheckUaDevaiceで調べてみると12985行目あたりに次のような記述もありました
// PCだったら飛ばす処理 
var BASE_PATH = "/"; 
var url = "https://jojo-polpotest.com"; 
var device = new _CheckDevice2.default(); 
device.checkUaDevaice(); 
  • というわけで、UAを偽装してしまえば簡単に突破できることが分かります
  • ChromeDevToolは先ほどのように端末を切り替えることでUAも変更されるので、これで解決というわけです


:bangbang: 問題2:期間を過ぎている

  • スマホ用ページにはアクセスできましたが、「本キャンペーンは終了しました」と表示されてしまいます
  • しかし、「elements」からこの要素を選択して削除してみると・・・


スクリーンショット 2019-02-04 22.39.24.png




スクリーンショット 2019-02-04 22.41.29.png


  • 「次へ進む」ボタンが現れました!
  • ただ残念ながら、このボタンはaタグではなくJavaScriptで動いているようです
  • 仕方なく先ほどのコードの続きを読んでみると・・・
//top 
var startTop = function startTop() { 
 
  (0, _jquery2.default)(".js-btnTab").on('click', function () { 
 
    (0, _jquery2.default)(".contentTop").fadeTo(1000, 0); 
    setTimeout(function () { 
      location.href = BASE_PATH + "notice/"; 
    }, 1000); 
  }); 
}; 


スクリーンショット 2019-02-04 23.00.36.png


  • 成功です!


:bangbang: 問題3:ページが存在しない

  • そのまま「試験を受ける」を選択して https://jojo-polpotest.com/story/ に進みますが、404 NotFound になってしまいます。流石にゲームのページは削除されてしまったようです
  • もはやここまで・・・と思いつつも、他に見れるページがないか探してみます。すると
(0, _jquery2.default)(function () { 
  var ID_PAGE = (0, _jquery2.default)("body").attr("id"); 
  switch (ID_PAGE) { 
    case "top": 
      startTop(); 
      break; 
    case "notice": 
      startNotice(); 
      break; 
    case "story": 
      startStory(); 
      break; 
    case "share": 
      startSharePage(); 
      break; 
    case "check": 
      checkFlgClear('top'); 
      break; 
    case "result": 
      checkFlgClear(false); 
      startFailure(); 
      break; 
    case "clear": 
      checkFlgClear(true); 
      startClear(); 
      break; 
    case "clear_c": 
      checkFlgClear(true); 
      startClearShare(); 
      break; 
    case "contact": 
      startContact(); 
      break; 
    default: 
  } 
}); 
  • bodyタグのidで処理を変えていて、さらにこのidはページのパスと対応しているようですね
  • 試しに先ほど見れなかったcase "story":startStoryを探してみると
var startStory = function startStory() { 
  var imgList = ['/images/story/story01.png', '/images/story/story02.png', '/images/story/story03.png', '/images/story/story04.png', '/images/story/story05.png', '/images/story/story06.png', '/images/story/story07.png', '/images/story/story08.png', '/images/story/story09.png', '/images/story/story10.png', '/images/story/story11.png', '/images/story/story12.png'], 
      fadeinList = [], 
      winHeight = (0, _jquery2.default)(window).height(); 
 
  (0, _jquery2.default)(".introArea li").each(function (index, elm) { 
    fadeinList[index] = { 
      top: (0, _jquery2.default)(elm).offset().top, 
      flg: false, 
      elm: elm 
    }; 
  }); 
  var scrollEvent = function scrollEvent() { 
    var ScrTop = (0, _jquery2.default)(document).scrollTop() + winHeight / 2; 
    _jquery2.default.each(fadeinList, function (index, val) { 
      if (val.top < ScrTop && val.flg == false) { 
        (0, _jquery2.default)(val.elm).addClass("is-fadein"); 
        val.flg = true; 
      } 
    }); 
  }; 
 
  var readImage = new _ReadImage2.default({ 
    targetImageUrl: imgList, 
    callback: function callback() { 
      console.log("loaded"); 
      scrollEvent(); 
    } 
  }); 
  (0, _jquery2.default)(window).scroll(function () { 
    scrollEvent(); 
  }); 
}; 
 
... 
 
var startSharePage = function startSharePage() { 
  location.href = BASE_PATH; 
}; 
  • スクロールに応じて画像を表示させる処理があったようですね
  • 画像のパスは生きているようで、https://jojo-polpotest.com/images/ にアクセスすると使用している画像の一覧が見えました
  • また、case "share":で使われているstartSharePage は、ただトップページに戻されるだけのようです
  • そしてcheckFlgClearにはクリア判定処理っぽいものがありました
var checkFlgClear = function checkFlgClear(check_flg) { 
  var result = getStorageData(); 
 
  if (result.isClear !== check_flg || check_flg == "top") { 
    if (check_flg == "top" && result.isClear) { 
      (0, _jquery2.default)("body").addClass("is-jumpClear"); 
      jump(); 
    } else { 
      jump(); 
    } 
  } 
  function jump() { 
    if (result.isClear) { 
      location.href = 'clear.html'; 
    } else { 
      location.href = "result.html"; 
    } 
  } 
}; 
 
  • ざっくり言うと、クリアの場合はclear.html、それ以外の場合はresult.htmlに飛ばされるようですね
  • しかし、試しに https://jojo-polpotest.com/result/ にアクセスしてみますが、一瞬表示されるもののトップページに飛ばされてしまいます


:thinking: Why?

  • 先ほどのcase "result":の処理を部分を確認してみると、startFailureという関数を呼んでいることに気づきます
// 失敗 
var startFailure = function startFailure() { 
  var result = getStorageData(); 
  (0, _jquery2.default)(".resultTimeN").text(result.resultTime); 
  if (!result.resultTime) { 
    location.href = BASE_PATH; 
  } 
  if (!result.Level || result.Level > 10) { 
    location.href = BASE_PATH; 
  } 
 
... 
  • resultがif文の条件に当てはまるとトップページに戻されてしまうようですね

  • var result = getStorageData();とあるのでgetStorageDataを探してみると次のような記述がありました
var getStorageData = function getStorageData() { 
  return { 
    'isClear': _store2.default.get('state'), 
    'startTime': _store2.default.get('startTime'), 
    'resultTime': _store2.default.get('resultTime'), 
    'Level': _store2.default.get('Level'), 
    'userName': _store2.default.get('userName') 
  }; 
}; 
  • localStorageからデータを取得しているようなので、localStorageにそれっぽい値を入れてみましょう
  • 「Application > Storage > Local Storage > https://jojo-polpotest.com」で確認&入力ができます


スクリーンショット 2019-02-05 00.06.26.png


  • これでクリアしたことにできました!


スクリーンショット 2019-02-05 00.08.24.png



:star: おまけ

  • localStorageのstateをfalseにし、Levelを1~10の間で変えると失敗した時の画面を見れます


スクリーンショット 2019-02-05 00.12.15.png


var resultInfo = [{ 
    "level": 1, 
    "name": "マンモーニだった頃のペッシ", 
    "unique": "1edrgiheg2" 
  }, { 
    "level": 2, 
    "name": "ナイフを顔面につき立てられてもケンカをやめなかった涙目のルカ", 
    "unique": "2fa23ayzy4" 
  }, { 
    "level": 3, 
    "name": "アバッキオに渡された紅茶が小便だと気付いた時のジョルノ", 
    "unique": "3ycuykygx2" 
  }, { 
    "level": 4, 
    "name": "敵を見失ったので道路中に火をつけるナランチャ", 
    "unique": "4zgna35a8h" 
  }, { 
    "level": 5, 
    "name": "鍵を守るため自らの手首を切り落とすアバッキオ", 
    "unique": "5x5xgymkxi" 
  }, { 
    "level": 6, 
    "name": "弾丸が自分に跳ね返ってくるとわかっていながら銃を撃ちまくるミスタ", 
    "unique": "6ebhkzs8jg" 
  }, { 
    "level": 7, 
    "name": "敵もろとも時速150kmの列車から飛び降りるブチャラティ", 
    "unique": "7hwm8znu8s" 
  }, { 
    "level": 8, 
    "name": "スタンドを発動させるため自ら殺されにいくカルネ(ノトーリアス・B・I・G)", 
    "unique": "8npsw56di5" 
  }, { 
    "level": 9, 
    "name": "命が尽きるまで攻撃をやめないプロシュート兄貴", 
    "unique": "9drtkn4di9" 
  }, { 
    "level": 10, 
    "name": "ボスのために自ら口封じを行うペリーコロさん", 
    "unique": "08hdpxrger" 
  }]; 
  • また、応募条件であったクリア時のツイート内容もソースに書いてあったりします
var startClearShare = function startClearShare() { 
  var result = getStorageData(); 
  if (!result.userName) { 
    location.href = BASE_PATH + "result/clear.html"; 
  } 
  (0, _jquery2.default)(".userName").text(result.userName); 
  (0, _jquery2.default)(".clearTimeTxt").text(result.resultTime); 
  var shearLink = 'https://twitter.com/share?url=' + url + '/result/' + CLEAR_BASE + '.html&text=%E3%80%90%23%E3%83%9D%E3%83%AB%E3%83%9D%E3%83%86%E3%82%B9%E3%83%88%E3%82%AF%E3%83%AA%E3%82%A2%E3%80%91%E3%81%93%E3%82%8C%E3%81%A7%E8%A6%8B%E4%BA%8B%E3%83%91%E3%83%83%E3%82%B7%E3%83%A7%E3%83%BC%E3%83%8D%E3%81%AE%E4%B8%80%E5%93%A1%E3%81%A0%E3%83%83%EF%BC%81%E6%97%A9%E9%80%9F%E3%80%81%E6%9C%80%E5%88%9D%E3%81%AE%E4%BB%BB%E5%8B%99%E3%80%8C%E5%AE%A3%E4%BC%9D%E3%80%8D%E3%82%92%E8%A1%8C%E3%81%86%E3%83%83%EF%BC%81%0D%0A%0D%0A%E3%80%8C%E8%8D%92%E6%9C%A8%E9%A3%9B%E5%91%82%E5%BD%A6%E5%8E%9F%E7%94%BB%E5%B1%95%E3%80%80JOJO%E3%80%80%E5%86%92%E9%99%BA%E3%81%AE%E6%B3%A2%E7%B4%8B%E3%80%8D%E3%80%88%E5%A4%A7%E9%98%AA%E4%BC%9A%E5%A0%B4%E3%80%89%0D%0A2018.11.25%EF%BC%88%E6%97%A5%EF%BC%89-%202019.1.14%EF%BC%88%E6%9C%88%EF%BC%89%0D%0A%EF%BC%A0%E5%A4%A7%E9%98%AA%E6%96%87%E5%8C%96%E9%A4%A8%E3%83%BB%E5%A4%A9%E4%BF%9D%E5%B1%B1%E3%81%AB%E3%81%A6%E9%96%8B%E5%82%AC%E3%83%83%EF%BC%81%E3%83%81%E3%82%B1%E3%83%83%E3%83%88%E7%B5%B6%E8%B3%9B%E7%99%BA%E5%A3%B2%E4%B8%AD%E3%82%A5%EF%BC%81%0D%0A%0D%0A%23%E3%82%B8%E3%83%A7%E3%82%B8%E3%83%A7%E5%B1%95'; 
  (0, _jquery2.default)("#btnTwClear").attr("href", shearLink); 
  (0, _jquery2.default)(".clearArea").fadeTo(1000, 1); 
}; 
shearLinkのtextの文字コードを変換するとこんな感じ
【#ポルポテストクリア】これで見事パッショーネの一員だッ!早速、最初の任務「宣伝」を行うッ! 
 
「荒木飛呂彦原画展 JOJO 冒険の波紋」〈大阪会場〉 
2018.11.25(日)- 2019.1.14(月) 
@大阪文化館・天保山にて開催ッ!チケット絶賛発売中ゥ! 
 
#ジョジョ展 https://jojo-polpotest.com/result/00wsu36mtn.html 


:star2: まとめ

  • 実はlocalStorageの値をいじって https://jojo-polpotest.com/result にアクセスするだけでポルポテストにクリアしたことになる(ちょっとパッショーネっぽい?)


:speech_balloon: 感想

  • 自分の場合はChromeDevToolsを使う習慣があった&真っ先にLocasStorageを覗きにいったので実はここまで見てなかった :innocent:
  • でも改めて他の人が作ったサービスをデバッグしてみる裏技を探してるみたいで面白かった
  • ブラック・サバスに襲われても責任は負いません

コメント

このブログの人気の投稿

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2021-04-30 23:37:32 RSSフィード2021-04-30 23:00 分まとめ(42件)

投稿時間:2023-02-05 02:09:04 RSSフィード2023-02-05 02:00 分まとめ(9件)