官方沒寫、Wiki 說「還在調查中」:我打開了遊戲的 38GB 封包,自己去找答案
目錄
官方沒寫、Wiki 說「還在調查中」:我打開了遊戲的 38GB 封包,自己去找答案
事情的起點很小:我想知道一款遊戲裡,哪些單位放在基地會有隱藏效果。
結果我發現——沒有人知道。
- 官方沒有文件
- 最大的社群資料庫,那隻單位的頁面寫著:"This Pal's abilities are still being investigated."(此單位的能力仍在調查中)
- 另一個常被引用的英文資料庫,收錄的還是改版前的舊資料
- 中文圈,完全沒有
那一刻我意識到一件事:我不是在等別人整理好答案,我是可以自己去拿答案的人。
於是我打開了遊戲的資料封包。兩小時後,我手上有一份全世界都還沒有的清單。
這篇是那兩小時的完整記錄。如果你不寫程式,也請往下看——因為真正重要的不是那 200 行程式碼,是那個判斷。
一、我面對的東西長什麼樣
遊戲安裝完之後,所有的資料——每一個單位的數值、每一句台詞、每一張表——都被打包進一個 38GB 的檔案裡。
你可以把它想成一個上了鎖的巨大壓縮檔:裡面有 18 萬 5 千個檔案,但你沒有解壓縮軟體,因為它用的是遊戲引擎自己的格式。
現成的工具是有,但那天我決定自己寫一個。原因很單純:我想知道那把鎖到底怎麼開。
二、五個步驟(每一步先講白話,程式碼放後面)
步驟 1:找到「目錄」在哪
白話:任何壓縮檔的結尾都會有一小段「說明書」,告訴你這包東西有幾個檔案、目錄在哪、有沒有加密。先讀它。
這一步讀出來的三行字,決定了後面所有的事:
版本 = 11
加密 = 否 ← 幸運,不用破解金鑰
壓縮 = Oodle ← 麻煩,這是專有格式
「加密 = 否」是這次能成功的關鍵。如果它加密了,我需要先從遊戲的記憶體裡挖出金鑰——那是完全不同等級的工程。
看程式碼
FOOTER = 16 + 1 + 4 + 4 + 8 + 8 + 20 + 32 * 5
f.seek(size - FOOTER)
foot = f.read(FOOTER)
guid, encflag = foot[:16], foot[16]
magic, ver, ioff, isize = struct.unpack("<IIQQ", foot[17:41])
assert magic == 0x5A6F12E1 # pak 的識別碼
步驟 2:把 18 萬個檔案的地圖攤開
白話:拿到「說明書」之後,順著它給的位置,就能讀到完整的檔案清單——等於拿到整個遊戲的地圖。
跑完的結果:
檔案數 = 185,003
目錄數 = 9,007
這一步的價值:從這裡開始,我可以像用搜尋一樣去找東西。我要的答案,關鍵字一搜就浮出來了:
.../DataTable/Text/DT_PalFirstActivatedInfoText.uexp ← 單位能力的官方說明
.../Config/DefaultPalWorldSettings.ini ← 官方預設值,純文字
步驟 3:解開被「壓扁」的位置資訊
白話:為了省空間,遊戲把每個檔案的「位置、大小、有沒有壓縮」全部塞進 4 個 byte 裡,用位元在記錄。同一個欄位,可能是 4 bytes 也可能是 8 bytes,看某個開關而定。
這是最容易寫錯的一步——搞錯一個位元,整份地圖就歪掉,你會讀到一堆亂碼還以為檔案壞了。
看程式碼
v = u32(encoded, o)
comp = (v >> 23) & 0x3F # 壓縮方式
nblocks = (v >> 6) & 0xFFFF # 區塊數
offset = u32 if v & (1<<31) else u64 # 寬度看 flag 決定!
usize = u32 if v & (1<<30) else u64
步驟 4:解壓縮(用我電腦裡已經有的東西)
白話:檔案是用 Oodle 壓的,那是一套要授權的專有技術,Python 沒有內建。
一般教學會叫你去網路上找 DLL。但我先做了一件更聰明的事:盤點我電腦裡已經有的東西。
我剛好裝過一個處理這款遊戲存檔的開源工具——它自己就帶了 Oodle 解壓器。直接接來用,五分鐘解決。
這一步的心法比技術重要:你需要的能力,常常已經躺在你為了別的目的裝過的某個工具裡。 先盤點,再造輪子。
步驟 5:繞過那道真正的牆
白話:解壓縮之後,我拿到了檔案,但還是讀不懂。
因為新版遊戲引擎為了效能,把「欄位名稱」從檔案裡拿掉了——檔案裡只剩下編號,要另一份對照表才知道「第 3 個欄位叫什麼」。而那份對照表通常要從執行中的遊戲記憶體裡 dump 出來。
這是這條路上真正的牆,我沒有翻過去。
但我要的東西是文字(能力說明),而文字有個特徵:它在檔案裡就是**「長度 + 內容」**這樣一段一段排好的。所以我不需要對照表,直接把所有文字掃出來就行了。
而且資料表的存法剛好是「名稱、內容、名稱、內容」交錯排列——掃出來的順序天然就是配對好的。
看程式碼
# 正數 = UTF-8,負數 = UTF-16,這是引擎的字串格式
ln = struct.unpack_from("<i", b, i)[0]
if 0 < ln < 400:
s = b[i+4 : i+4+ln]
elif -400 < ln < 0:
s = b[i+4 : i+4+(-ln*2)].decode("utf-16-le")
# 資料表是「名稱、內容」交錯,所以下一條就是答案
for i, s in enumerate(strings):
if s.endswith("_TextData"):
rows[s[:-9]] = strings[i+1]
這招不萬能——結構化的數值(例如「+30%」那個 30)我還是讀不出來,那些真的需要對照表。但對文字表 100% 夠用。
三、最重要的一步:證明我沒挖錯
這段跟技術無關,但它是整篇的核心。
挖到資料只是一半。你必須證明它是完整而且正確的,否則你只是在製造更精緻的假消息。
我做了三層檢查:
① 有沒有漏? 解析出來的筆數,跟表格裡實際的筆數對得上嗎? → 305 筆,配對 305 筆,零遺漏。如果我的程式有 bug,這裡一定會對不上。
② 有沒有看走眼? 我打算宣稱「全遊戲只有一個單位有防禦效果」。那我就換一批同義詞(入侵、砲轟、迎擊、盤旋、防衛)把整張表重掃一遍,確認沒有漏網之魚。 → 掃完只有那一個。這時「只有」兩個字才敢寫出來。
③ 有沒有跟其他資料互相矛盾? 我原本要引用一個外站的說法(「某個數值的上限是 10」)。 → 我在遊戲檔案裡翻遍所有文字表都找不到任何佐證。
所以我把那句話刪掉了。
寧可少講一句,不要講錯一句。逆向工程最大的風險從來不是「解不開」,而是**「解開了、理解錯了,還很有自信地公開」**。
四、結果
- 一支 約 200 行、零第三方依賴的封包讀取器
- 從 38GB 裡精準取出需要的 3 張資料表 + 1 個官方設定檔
- 得到一份官方沒公開、Wiki 還沒有、中文圈完全沒有的清單
- 順帶推翻了一條原本要引用的外站說法
而且這支工具不綁這款遊戲——任何用同樣引擎打包的遊戲都能用。
五、為什麼 Ultra Lab 要寫這個
表面上這是遊戲考據。但它其實是我們每天在做的事的縮影:
當「權威來源」說不知道的時候,去讀原始的那一層。
我們做 UltraProbe(開源的 AI 安全掃描器)就是同一件事——我們不看廠商的行銷話術說自己多安全,而是直接去掃它的 prompt、去看它的 agent 實際做了什麼。
答案通常不在文件裡。在二進位檔裡、在流量裡、在它實際跑起來的行為裡。
工具會過時,這個習慣不會。
邊界說明
- 全程唯讀,沒有修改任何遊戲檔案。
- 不散布遊戲資產或整包文字。本文呈現的是我整理出的結論和方法。
- 機制與數值屬於事實陳述,寫成攻略沒問題;遊戲資產有著作權,那是另一回事。這條線要自己守好。