
“Go catch em all! ”
前言
在一個月前, Pokemon Go 成了新一輪現(xiàn)象級手游?;?LBS (Location Based Service) 的設(shè)計給社交帶來了更多可能。
從最早玩游戲的時候,我就不甘于受限于游戲的世界,探索各種 “作弊” 的方式。既然是基于位置的游戲,那么制作一個 “地圖” 也成了順其自然的想法。
這篇文章就記錄了我從反向工程解構(gòu) Pokemon Go 架構(gòu),到建立一個實時 Pokemon 地圖的過程。
成品: mypokemon.io
探索
對網(wǎng)絡(luò)安全有一些了解的同學(xué)大概都知道,要 hack 某個游戲或者 app ,你需要做的第一件事就是了解它是怎樣運作的。對于 Pokemon Go 也是一樣的。
監(jiān)聽網(wǎng)絡(luò)流量
首先,我需要架設(shè)一個網(wǎng)絡(luò)流量監(jiān)聽的工具。由于近年來 REST API 的流行,我假設(shè) Pokemon Go 也是用 REST API 來通信的,那么假設(shè)一個 http proxy 便能夠進行 MITM ( man in the middle attack ) 攻擊,并截取通信記錄了。在這里,我用的工具是 burp
由于我的 macbook 和我的手機接入到了同一個 wifi ,我在電腦上設(shè)置的 proxy ,在 iphone 上也能連上。在 burp configure 界面有很詳細的設(shè)置過程,在設(shè)置好電腦跟手機之后,我們就可以在 burp 上看到 Pokemon Go 的網(wǎng)絡(luò)流量了。

解碼
在 burp 里,我們可以看到 Pokemon Go 的 通信記錄 ,其中大部分都是毫無意義的二進制碼。聯(lián)想一下 Google 的技術(shù)棧,不難想到,這是用 protocol buffer 編碼過的,那么用 protobuf 反向解碼,就可以看到原請求了。
我寫了一個 小腳本 來批量解碼, 解碼的結(jié)果 是可以 json 格式的 enum 集合。由于沒有 schema ,下一步我們需要做的就是搞清楚每個 enum 對應(yīng)的意思。這個過程就比較枯燥繁瑣了,在 github 上效率最高的 project 算是 AeonLucid/POGOProtos ,我們就不要重新造輪子了。
同樣的,在這步之后,把 protocol buffer 轉(zhuǎn)化成可用的 API 也有開源的項目 tejado/pgoapi ,我們就可以跳過這兩步,直接開始設(shè)計地圖了。
架構(gòu)設(shè)計
我最終的目標(biāo)是提供一個 Pokemon Map as a Service,那么所有的數(shù)據(jù)儲存跟采集都必然發(fā)生在云端。我決定的架構(gòu)是這樣的:

整個系統(tǒng)分為 3 個部分:
網(wǎng)頁前端
數(shù)據(jù)查詢層
數(shù)據(jù)采集層
網(wǎng)頁前端
由于地圖的本身的特性,網(wǎng)頁前端是非常輕量級的,基本不需要怎么修改,只需要顯示后端發(fā)回的數(shù)據(jù)即可。所以前端我就直接用 github pages 來托管。
在最新的 chrome 瀏覽器里,實時定位信息只能在 https 顯示的網(wǎng)頁上使用,所以為了能夠使用用戶的位置信息,我用 cloudflare 做 DNS 提供商。用 cloudflare 不光可以一鍵增加 SSL 證書,還可以提供 CDN 服務(wù),這樣也解決了一部分前端加載速度的問題。
數(shù)據(jù)查詢層
將數(shù)據(jù)查詢跟數(shù)據(jù)采集分開也是很自然的想法。地圖上的數(shù)據(jù)在短時間內(nèi)并不會有巨大的改變,所以查詢這個動作是很輕量級的,但是數(shù)據(jù)采集則需要從服務(wù)器爬取信息,是需要很多集群資源的。
在這里,我用 AWS 的 API Gateway 來給網(wǎng)頁端提供接口,API Gateway 再把請求轉(zhuǎn)發(fā)給一個 AWS Elastic Beanstalk 的 Django 后端,最后轉(zhuǎn)化成一個簡單的 SQL。由于目前并沒有很大的流量,所以即使直接 query 數(shù)據(jù)庫延遲也很低。如果今后流量增大了,也許會考慮再加一個 Elastic Search 來提高效率。
數(shù)據(jù)采集層
數(shù)據(jù)采集才是這個地圖中最核心的部分。要設(shè)計數(shù)據(jù)采集層,我們就要先看看有哪些可以用的工具。
在前面我們已經(jīng)得到了可以模擬 Pokemon Go 客戶端的 python api,在 Pokemon Go 中,每 5 秒,客戶端就會發(fā)送一個 GET_MAP_OBJECTS 請求給服務(wù)器,包含了用戶所在的位置,服務(wù)器則會返回用戶可以抓到的小精靈地址,以及離用戶很近的小精靈地址。這些小精靈大概是在距離用戶 100 米內(nèi)的。這就意味著,每一個用戶請求,只能覆蓋半徑 100 米的圓形區(qū)域,要搜索 1 平方公里的區(qū)域,就需要發(fā)送約 100 個請求到 Pokemon Go 服務(wù)器。 例子