蒼時弦也
蒼時弦也
資深軟體工程師
發表於

COSCUP 2019 - 演講後談復活的頁遊 - Unlight (一)

COSCUP 分享了這兩週左右(8/3 ~ 8/17)把一款決定開放原始碼的網頁遊戲,從無法啟動到恢復伺服器開始運作的一些經驗跟大家分享。 不過看起來還是有很多人沒有機會來聽,雖然之後因為會把一部分重心放在這款遊戲上,所以應該還是有不少機會,但還是簡單的來彙整一下今天講的東西。

上一篇快速閱讀 Unlight 原始碼大致上有提到了我在當時看到原始碼的看法跟概觀。有興趣的話可以搭配演講簡報一起讀這篇文章。

另外,這次整個遊戲運作起來除了我自己本身對 Ruby / ActionScript 有一定的了解外,也要感謝一下我們這個團隊(Open Unlight)的初期成員 Poka 和舞鶴,給我硬體上的支援跟對其他玩家的客服支援,不然有時候真的很難同時處理這些事情。

動機

最開始只是我想自己研究,主要是這幾點原因在。

第一點是日本的遊戲公司很少會放出原始碼,至少以現在很熱門的手遊大家幾乎是沒機會看到開放原始碼的專案,不過即使是歐美也是,所以變得很難有機會學習怎麼做。 第二點則是因為他用了 Ruby 和 ActionScript 這兩個語言,前者是我工作用的語言也是我個人很喜歡的語言,後者是我大學寫過一陣子的語言,兩個我都蠻熟悉的。

後來發現伺服器用 DigitalOcean 沒辦法支撐後,朋友 Poka 就把家裡閒置的機器拉出來提供給我使用,所以又再加上把一些工作上很少有機會用到的理論套上去,因為玩家數夠多了!

注意:不是每個朋友家裡都剛好會有空的機櫃、伺服器跟有冷氣的機房!

復原

遊戲畫面

其實伺服器部分本身沒有什麼大問題,基本上就是缺幾個檔案。不過這些檔案只是被改成 _orig 的形式,把檔名改回去後就好了,基本上是一些設定檔像是伺服器的 IP 位置、資料庫設定等等。

至於 Flash 客戶端的部分,因為想說是 Flash 所以就跑去想把 Flash IDE 裝回來,畢竟一般來說都是靠 IDE 來處理的。不過因為看起來像是用 Flex 就選擇安裝 Flash Builder 來跑,結果就直接噴出 Failed to create Java Virtual Machine 的錯誤,這是因為新版的 macOS 用的 Java 已經新太多。

而且 Adobe 還把這些 IDE 都下架,所以光是想要取得就有不少困難在。

不過因為是用 Flex 所以還有 Flex SDK 可以替代,至少能用 Command Line 的方式去編譯,結果當時(週五晚上)由 CPA 釋出的原始碼裡面缺少了最重要的 src/ 目錄,也就是客戶端本體的實作部分。 大概過了兩天,到週一的時候才發現少上傳補上到 GitHub 上面,此時就能透過 mxmlc 這個指令去編譯。

回頭來看,我們會認為 Flash 就是用 IDE 用 GUI 去寫這個專案,但其實 Unlight 幾乎是直接用 ActionScript 寫的,除了美術的部分合理推測會使用 Flash 的 IDE 之外,其實遊戲本身大概是沒有使用的。

缺少的 as 檔案

不過即使取得了完整原始碼,還是發現這樣子還是有缺檔案的樣子。經過追查之後,發現缺少的 FontLoader.asConstants.as 應該都是由 Ruby 的腳本生成。

所以找了一下,發現在伺服器端確實有腳本需要執行過才能正常生成客戶端。

Constants.as - 一些文本資料或不影響平衡的都可以塞到客戶端,這個檔案基本上應該就是將 DB 的資料直接整到客戶端的主要方式。 FontLoader.as - Unlight 當時算是非常熱門的遊戲,所以至少有五到六種語言的翻譯,為了遊戲美術表現的品質所以會把字體內嵌,因此會使用 Ruby 負責掃瞄出這個語言客戶端所需的 Unicode 來告訴 Flex Compiler 需要內嵌哪些字體檔案。

基本上是蠻合理的設計,不過因為我們大部分都是做 Web 習慣的,可能會不太理解吧 XD

語言切換

令一方面因為多國語言,所以其實有很多 .swf 檔案都會用像是 _tc.swf _ja.swf 這樣的形式命名,假設原始檔案叫做 tutorial.swf 的話,一開始是不會有這個檔案的,因為你需要利用一個叫做 switch_resouce.rb 的檔案,幫你從 tutorial_tc.swf 重新命名成 tutorial.swf 才行。

但是因為他是用 Git 的方式讓大家下載的,所以最後就會讓整個 Repo 裡面充滿各種沒有被追蹤的檔案,也會非常混亂。

除此之外部署的時候如果沒有切換的話,在遊戲過程中還會因為抓不到對應的 swf 造成許多地方無法正常運作。

Docker 化

基於前面這些編譯客戶端的問題,最簡單的方法就是用 Docker 來處理。另一方面就是要部署起來其實需要不少額外的補丁,而這些處理目前是否能 PR 回官方的版本,或者直接修改維護都還是問題。雖然 Docker 不是一個萬用的解決方案,但是在想要有一個乾淨的編譯環境來說,反而是一個相對適合的做法。

在編譯客戶端的時候,只需要將圖檔這些東西都複製進去 Docker 裡面,然後將前面設定 Java 環境(Flex 是基於 Java)、Flex SDK 設定以及各種補丁和我自己修改過的檔案,全部加入到容器裡面,再用做一次性的編譯環境就可以很方便的產生客戶端。

某方面來說可以用時間換來一定程度相對乾淨的專案環境

令一方面因為 Unlight 的設計很適合讓思考 SOA / Microservice 之類的架構設計思考方式,因為它將登入伺服器、任務伺服器等等都切割開來,所以要部署伺服器起來其實也要做不少動作,但假設我們使用 Docker Compose 的話,就可以透過設定檔去解決這些問題。

登入失敗

經過幾天的努力總算是有能執行的客戶端以及可以運行的伺服器,不過在修復客戶端時使用了網路上下載的 as3crypto.swc 這個套件,結果卻一直無法登入。

理論上加密演算法不應該不一樣,但是因為各種理由使用 CPA 後來(一週後)提供的檔案,就能正成使用。

Unlight 使用了一個叫做 Secure Remote Passwor 的機制,在做簡報的時候發現他的實作裡面有一個叫做 OpenSSL 所以還蠻好懂的,我們在開發網站很熟悉的 SSL 憑證基本上也基於這個 Stanford 的論文來實現的,不過中間遭遇了很多問題。

因為遊戲使用的是「論文版本」也就是完全參照論文去實現,但是網路上像是 1Password 的 Golang 版本或者 Mozilla 的 Node.js 版本,都是基於 RFC5054 的版本差異上來說就是 Salt 在客戶端的先後順序。

論文版本是由客戶端先發送 Public Key 到伺服器換取 Salt 跟伺服器的 Public Key 在繼續後面的運算,但是 RFC 版本使用的則是客戶端先發送請求初始化連線,再從伺服器拿到 Public Key 和 Salt 這些資訊,最後再生成 Public Key 發給伺服器後做後續的運算。

兩個版本順序剛好是相反的,也讓我很難用其他語言實作來驗證看看。

其實 RFC 還有其他版本,不過目前主流的應該是 RFC5054 的這個版本。另外還有一個好處是當伺服器更改了計算的基準(N, g)的時候,因為伺服器發回來的也包含這兩個數值,所以就不需要修改客戶端,至少在安全性跟方便性都有所提高。

效能調校

因為 Poka 支援了伺服器,所以我們實際上是有 24 Core & 24G RAM 的伺服器,連硬碟都是用有著 98K IOPS 的 SSD 理論上是不應該卡頓的⋯⋯

不過根據我前兩天在 Digital Ocean 用 $5 和 $15 的機器測試的狀況,大概是 60 人跟 300 人會明顯卡頓,但是換到了 Poka 的機器上硬體至少提升了有十幾倍,卻還是卡在 500 多人就很不合理。

中間我們做了很多嘗試,像是利用 Docker 可以限制使用的 CPU 核心之類的設定將 DB 集中在某個 CPU 上之類,還有調整記憶體限制等等,最後才發現了一筆異常的數值。

因為 SSD 的關係遊戲每個 SQL 操作大概是在 0.0001s 左右,但是有一個很頻繁的 SQL 操作大概花上 0.2s 左右經過補上 Index 的處理後,遊戲直接達到 1000 人左右都不卡頓的狀況。

當時有玩家還在猜該不會當初遊戲公司死掉也跟這個少掉的 Index 有關吧?不過理論上以當時的玩家數量,不太可能少處理這點。

就我跟 Poka 的評估,目前使用的伺服器即使到了 3000 多人也應該能支撐住才對。不過到了最近玩家人數開始穩定的減少,推測差不多應該會停在一個負擔不太大的數值上吧 XD

畢竟很多玩家都是來懷舊的⋯⋯

小結

雖然已經盡量寫的簡單,但是似乎文章還是有點太長 XD

下一篇會再聊一下後續的處理,也就是經過我修改過的 Docker 版本提供了選擇性客製化的功能以及我們怎麼在經營遊戲上處理盡量讓玩家有一個相對好的體驗、以及對 HTML5 化的規劃跟實驗。