reverse-engineeringunreal-enginepythondataminingoodle

官方沒寫、Wiki 說「還在調查中」:我打開了遊戲的 38GB 封包,自己去找答案

· 10 分鐘閱讀
目錄
  1. 一、我面對的東西長什麼樣
  2. 二、五個步驟(每一步先講白話,程式碼放後面)
  3. 步驟 1:找到「目錄」在哪
  4. 步驟 2:把 18 萬個檔案的地圖攤開
  5. 步驟 3:解開被「壓扁」的位置資訊
  6. 步驟 4:解壓縮(用我電腦裡已經有的東西)
  7. 步驟 5:繞過那道真正的牆
  8. 三、最重要的一步:證明我沒挖錯
  9. 四、結果
  10. 五、為什麼 Ultra Lab 要寫這個
  11. 邊界說明

官方沒寫、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 實際做了什麼

答案通常不在文件裡。在二進位檔裡、在流量裡、在它實際跑起來的行為裡。

工具會過時,這個習慣不會。


邊界說明

  • 全程唯讀,沒有修改任何遊戲檔案。
  • 不散布遊戲資產或整包文字。本文呈現的是我整理出的結論方法
  • 機制與數值屬於事實陳述,寫成攻略沒問題;遊戲資產有著作權,那是另一回事。這條線要自己守好。

每週 AI 自動化實戰筆記

不廢話,只有能直接用的東西。Prompt 模板、自動化 SOP、技術拆解。

加入一人公司實驗室

免費資源包、每日建造日誌、可以對話的 AI Agent。一群用 AI 武裝自己的獨立開發者社群。

需要技術協助?

免費諮詢,24 小時內回覆。