速查 › 封裝格式
BIGF 封裝格式
遊戲裡幾乎所有資源都裝在 .big 檔裡 —— 本站測試機的 data\ 底下,2026-09-05 數是 400 個(檔頭真的是 BIGF 的 383 個);剛安裝好的原版是 224 個(BIGF 207 個;多出來的是後來裝的球場、大頭照與備份包)。
它是 EA 自家的封裝格式,結構出乎意料地簡單:一個 16 bytes 檔頭、一份目錄、然後是資料。
但寫回去有一個會毀檔的陷阱,本頁最後一節專講這件事。
檔頭:16 bytes
+0x00 4 bytes magic「BIGF」
+0x04 4 bytes 檔案總大小 ← 多數是 little-endian,但有例外,見下
+0x08 4 bytes 項目數量 big-endian
+0x0C 4 bytes 目錄區結束位置 big-endian ← 不是第一筆資料的位置,見下
我們改過這一段:+0x0C 不是「資料起始位置」
這頁上線時把 +0x0C 標成「資料起始位置」。拿剛安裝好的原版 207 個封裝檔逐一驗:
- 它等於目錄區結束之後的位置(檔頭 + 目錄 + 8 個位元組的尾標)—— 207 / 207 全中,零例外。
- 但它不等於第一筆資料真正的位置:只有 18 / 207 相同。 其餘 189 個中間隔著填充,其中 183 個是 1 到 3 個位元組,另外 6 個更大(11、14、19、86、96、119)。
要找資料,請讀目錄裡那一筆自己的 offset,不要拿這個欄位推。
⚠ 一手發現:檔頭是混合位元組序
同一個檔頭裡,總大小是 little-endian,項目數和資料起始是 big-endian。 這在 EA 的格式文件裡沒寫,全部當成 big-endian 讀會得到一個荒謬的檔案大小。
實測證據:misc.big 的 offset 4 位元組是
7E 03 00 00。當成 big-endian 讀是 2,114,125,824 bytes(2 GB,明顯錯誤);
當成 little-endian 讀是 894,正是該檔的真實大小。
目錄:每個項目一筆
接在檔頭後面,每筆長度不固定(因為檔名長度不一):
4 bytes 資料位置(offset) big-endian
4 bytes 資料長度(size) big-endian
N bytes 檔名 latin-1,以 \0 結尾,沒有長度前綴
目錄結束後有一段 8 bytes 的尾標(L234 + 4 bytes 旗標),
之後才是實際資料區。(資料區沒有固定的對齊規則。原版 207 個封裝檔裡,第一筆資料落在 128 倍數上的只有 8 個 —— misc.big 剛好是其中之一。不要拿它推論通則。)
用 Python 讀目錄
# 完整可用版本見任一教學附的腳本,這裡是最小示範
import struct
data = open('ingame.big', 'rb').read()
assert data[:4] == b'BIGF'
count = int.from_bytes(data[8:12], 'big') # 項目數:big-endian
pos = 16
for _ in range(count):
offset, size = struct.unpack('>II', data[pos:pos+8])
pos += 8
end = data.find(b'\x00', pos) # 檔名到 \0 為止
name = data[pos:end].decode('latin-1')
pos = end + 1
print(f'{name:<32} {offset:>10} {size:>10}')
「孤兒資料」是什麼,以及本站怎麼算它
封裝檔裡常常有目錄沒有指向的位元組。本站叫它孤兒資料。 算法只有一個定義:
孤兒 = 檔案總長 − 檔頭 16 − 目錄長度 − 目錄指到的長度總和
關鍵是那個「減目錄長度」。 目錄本身也是沒有人指向它的位元組,但它不是孤兒 —— 它是這個格式必要的一部分。少減它會多算一點點。
⚠️ 2026-08-28:本站自己混用過兩種算法
同一個檔(測試機的 ingame.big)在站上出現過兩個孤兒數字,
差 1,495,而且從來沒有說明過差在哪。查出來是:
| 算法 | 結果 | 差在哪 |
|---|---|---|
| 本站現在的定義 | 2,468,068 | 檔頭與目錄都不算孤兒 |
| 另一個舊數字 | 2,469,563 | 把檔頭 16 與目錄 1,479 也算進孤兒 |
16 + 1,479 = 1,495 —— 完全對上。
兩個數字都不是筆誤,是兩種算法。已統一成上面那個定義。
用同一個定義重算一次
下面全部是用上面那個定義量出來的,換算方式一致,可以互相比較:
| 量的是什麼 | 總長 | 孤兒 | 比例 |
|---|---|---|---|
ingame.big 剛安裝好的原版 | 167,682 | 71 | 0.04% |
ingame.big 本站測試機(裝過很多模組) | 2,665,562 | 2,468,068 | 92.59% |
| 球場檔 87 個 剛安裝好的原版(中位數) | — | — | 0.00%(最高 0.01%) |
| 球場檔 87 個 本站測試機(中位數) | — | — | 40.06%(最高 68.01%) |
同一個檔名,兩份檔案,比例差 2,200 倍。 (92.590% 除以 0.04234% 是 2,187;上表那一欄四捨五入成 0.04%,拿捨入後的數字去除會多算成 2,300。) 所以站上任何一個孤兒數字,都要先問量的是哪一份 —— 「92.6%」講的是本站測試機那一份,不是這個格式的性質。 剛出貨的原版幾乎沒有孤兒(中位數 0.00%), 孤兒是後來一次次用安全改法疊出來的。
⚠️ 站上另一處寫「剛安裝好的原版孤兒只有 0.9%」,
那句用的是舊算法(把檔頭與目錄算進去)量 ingame.big。
換成現在的定義是 0.04%。已一併訂正。
實測數據
| 封裝檔 | 內容 | 項目數 |
|---|---|---|
| ingame.big | 比賽中的介面版面檔(FEL)(這是本站測試機那一份;剛安裝好的原版是 44 項) | 47 |
| frontend.big | 選單介面版面檔 | 196 |
| anims.big | 動畫三件套 | 730 |
| stadiums.big | 球場縮圖(.fsh) | 93 |
| schedule.big | 賽程 CSV(教學) | 9 |
讀寫 round-trip 測試(讀進來原樣寫回,比對是否位元組相同):294 / 294 全數通過。
另做過注入測試:frontend.big 196 項加一項變 197,重新開啟驗證檔名與內容皆正確。
⚠ 最重要的一節:寫回去的方式
讀取很單純,寫回才是會毀檔的地方。直覺做法是「把所有項目讀出來、改完再重新打包」—— 這會出事。
ingame.big 有 92.6% 的內容,目錄根本沒有指向
把 47 個項目的長度全部加起來只有 195,999 bytes, 但檔案本身是 2,665,562 bytes。 剩下那 2,468,068 bytes(92.59%)是目錄不指向、但確實還躺在檔案裡的資料。
用「重新打包」方式存檔,實測檔案會從 2.5 MB 變成 198,071 bytes。 47 個項目都還在、內容檢查也全過、不會有任何錯誤訊息 —— 問題可能等到遊戲跑進某個特定畫面才爆出來。
正確做法:接到檔尾
不要重排任何東西,只做三件事:
- 把新的資料接在檔案最後面
- 改目錄中該項目的 8 個位元組(新的位置與長度)
- 改檔頭的 4 個位元組(檔案總大小,先讀一次確認是哪一種位元組順序,見下方訂正卡)
原始資料區一個位元組都不動。檔案會變大一點,但什麼都不會遺失。
我們改過這一段:那個「little-endian」不是通則
這頁上線時,檔頭第 4 個位元組起的「檔案總大小」直接標成 little-endian。
後來把這台機器上的封裝檔都量過一次(當時 295 個真的是 BIGF,不含使用者自己複製的那份 data 備份;同一批裡副檔名是 .big 的有 312 個,差的那 17 個檔頭根本不是 BIGF,見本頁最後一節),結果是:
| 檔頭那一欄的位元組順序 | 檔數 | 哪些 |
|---|---|---|
| little-endian | 288 | 絕大多數,包含本站教學動到的 ingame.big、schedule.big |
| big-endian | 7 | models.big、portrait.big、pnamedat.big、
pnamehdr.big、三個球場夜間檔 |
| 兩種都對不上 | 0 | 沒有 |
所以寫回去之前先讀一次確認。用哪一種讀出來等於實際檔案大小,就用哪一種寫回去。 照字面一律寫 little-endian,會在上面那 7 個檔裡寫進跟原檔不同的位元組。
⚠️ 2026-09-05 訂正:上面那張表是當時量的。那之後這台機器的 data\ 又被動過 ——
多了一個封存原廠球場的資料夾,stadium\ 本身也換成球場模組那一版。
同一個資料夾用檔頭重數一次是 384 個封裝檔:374 個 little-endian、10 個 big-endian、0 個兩種都對不上
(用檔頭認,所以連一個跟著教學做出來的 schedule.big.bak 也算在內)。
多出來的 3 個 big-endian 不是新的檔名,是同樣那三個球場夜間檔在另一個球場資料夾裡又有一份。
站上幾課教學寫的「384 個封裝檔裡有 10 個」就是這一次。結論沒有變,變的只是分母。
後來這一條查清楚了。把每個封裝檔的位元組順序, 跟它的檔案修改時間交叉比對:
| 檔案的修改時間 | little-endian | big-endian |
|---|---|---|
| 2004-2005(遊戲出廠那兩年) | 146 | 0 |
| 2012 年以後(社群改過的) | 142 | 7 |
出廠期的檔案沒有半個是 big-endian。 那 7 個 big-endian 的檔,時間戳分別落在 2012、2013 與 2023 年, 全部是後來被社群工具重新打包過的。
⚠️ 2026-09-05 重跑:上面那張表是當時那 295 個的結果。
同一套量法在同一台機器上重跑那 384 個封裝檔,出廠期是
175 個 little-endian、big-endian 仍然掛零,
2012 年以後是 199 個 little-endian、10 個 big-endian。
多出來的是後來擺進 data\ 的球場資料夾副本。結論沒有變。
最直接的一組證據是球場檔:這台機器上同時留著同一個檔名的好幾份
dodgnite.big,一份在封存原廠球場的資料夾裡,
時間是 2004-12-24、little-endian;
另一份是球場模組那一套(現在遊戲用的 stadium\ 放的就是它),時間是 2013-10-19、big-endian。
同一個檔名、同樣 88 個項目,只有這一欄的位元組順序不同。
後來拿到真正的原版,直接驗證了這件事。
本站現在有一份剛安裝好、從未被改動過的英文版
(資料夾裡 1,191 個檔,其中 1,189 個的修改時間落在 2004-2005 年)。
⚠️ 這裡的「1,191」是資料夾裡有幾個檔。
另外兩個是 2003 年的 TP03Patcher.dll 與
2015 年的 filelist.txt ——
後者顯然不是安裝程式產生的,所以「安裝產物」算起來是 1,190 個。
本頁後面提到的 1,191 是前一種口徑,兩個數字都對,差別在算不算那一個。
那份剛安裝好的原版英文版裡,207 個封裝檔全部是 little-endian,一個 big-endian 都沒有;
另一份剛安裝好的原版繁體中文版,data 資料夾裡的 205 個封裝檔也全部是 little-endian。
這不再是從時間戳推的,是拿原廠安裝直接數出來的。
所以寫回去的規則可以講得很明確:EA 原廠一律 little-endian。 但如果你手上那個檔已經被別人的工具打包過,它可能是 big-endian —— 先讀一次,原檔是哪一種就照哪一種寫回去,這樣不管拿到哪一份都不會寫錯。
data[field:field+8] = struct.pack('>II', new_offset, len(payload)) # 目錄:BE
data[4:8] = struct.pack('<I', len(data)) # 檔頭:LE
這正是那 92.6% 的來源
這個檔案一路就是被這樣更新過來的,而遊戲始終讀得動 —— 等於這種存檔方式已經被十幾年的實際使用驗證過。
那片區域不是垃圾:裡面撈得出 13 個未壓縮的版面檔文字區塊, 合計 690,059 bytes,全部讀得出來。詳見 一個檔案裡的地層。
我們改過這一段
這頁上線時,這裡寫「殘骸區撈得出 156 種舊版畫面代號
(HUDLEFT1 一路到 HUDLEFT16,而現行版本只有一個 HUDLEFT)」。
重新量測後:156 這個數字量得出來,只是口徑不同。
它是把整片沒被指向的位元組一起掃的結果;
只看那 13 個讀得出整段文字的區塊是 143 種
(多出來的 13 個只出現在讀不成整段文字的地方,其中 3 個還是被壓縮資料切壞的名字)。
真正不對的是「舊版」兩個字 —— 這些畫面代號每一種現行版本裡都還在。
至於 HUDLEFT,現行的
fes_hudleft.fel 有 26 個
(HUDLEFT 加上 HUDLEFT1–HUDLEFT25),
不是一個 —— 這一點與本站
FEL 那頁原本就寫的數字相符,是這裡寫錯了。
每個前端封裝檔的頭尾各有一個 1 位元組的標記(2026-08-28 量的)
把剛安裝好的原版 data\frontend\ 底下每個裝圖片的封裝檔攤開來數,
會看到一件一開始像是壞掉的事:目錄裡有一個名字出現兩次。
| 封裝檔 | 重複的名字 | 在目錄的第幾筆 | 兩份的長度 |
|---|---|---|---|
portrait.big | a2653.fsh | 第 0 筆 與 第 2,392 筆(共 2,393) | 各 1 byte |
uniforms.big | a022.fsh | 第 0 筆 與 第 556 筆(共 557) | 各 1 byte |
stadiums.big | a1a01.fsh | 第 0 筆 與 第 92 筆(共 93) | 各 1 byte |
bkgnds.big | a22bd.fsh | 第 0 筆 與 第 106 筆(共 107) | 各 1 byte |
| …另外 13 個 | 都是 a 開頭 | 同樣是第一筆與最後一筆 | 各 1 byte |
17 個封裝檔,17 個都是這樣,零例外,而且官方英文版與官方繁體中文版完全相同。
它們是一對書擋
- 第一份坐在資料區的開頭 —— 目錄結束、資料開始的那一格
- 第二份坐在檔案的最後一個位元組 —— 它的位置正好等於「檔案長度減一」,17 個檔全部精確吻合
所以它們標的是資料區的頭與尾。名字也不是隨機的:
aaw… 在 awards.big、abi… 在 bpitems.big、
acp… 在 coopplyr.big —— 字首對應封裝檔自己的縮寫。
⚠️ 寫工具的人一定要知道這件事
- 用檔名當鍵建字典,會靜靜地少一筆。
兩筆同名,後面那筆會蓋掉前面那筆 ——
不會報錯,只是數字對不上。本站量
bkgnds.big時 先得到 106,跟先前記的 107 差一個,追下去才發現這件事 - 算「目錄指到多少位元組」時它們也在裡面,只是各佔 1 個位元組, 對總量沒有影響
- 可以拿來當完整性檢查:前端的圖片封裝檔, 第一筆與最後一筆的名字應該一樣、都是 1 個位元組、 而最後一筆的位置應該等於檔長減一。三個條件有一個不成立,這個檔就被動過
球場那些封裝檔沒有這個結構(本站量的 87 個一個都沒有), 所以這是前端封裝檔的慣例,不是 BIGF 格式本身的規定。
封裝檔裡面還有封裝檔
有兩個檔解開之後,裡面裝的是另一個封裝檔:
| 外層 | 外層大小 | 裡面唯一的項目 | 解壓後 | 內層項目數 |
|---|---|---|---|---|
| data\initstaz.big | 1,212,711 | initstay.big | 3,082,320 | 34 |
| data\initprgz.big | 107,019 | initpurge.big | 332,704 | 11 |
兩個都是外層只裝一個項目,那個項目是 QFS 壓縮的,解開就是一個完整的 BIGF。
外層檔名結尾是 z,內層是原本的名字 —— initstaz 裝 initstay、
initprgz 裝 initpurge。
為什麼要這樣包,檔案裡沒有寫
只有在這一層,項目名稱才會帶資料夾
本站把原版全部 207 個真的是 BIGF 的封裝檔目錄讀過一遍,
共 29,816 個項目:
| 項目名稱長什麼樣 | 幾個 |
|---|---|
單純的檔名(ball.fsh、2118.fsh) | 29,816 |
帶資料夾路徑(models/ball.o) | 0 |
可是拆開那兩個內層之後,45 個項目全部帶路徑:
initstay.big 的前幾個項目
models/bat.o 12,768
models/brknbat.o 17,536
models/ball.o 5,680
models/hibody.ord 786,336
misc/…
內層前置資料夾:initstay 是 models 33 個、misc 1 個;
initpurge 是 models 11 個。
這解釋了一件老玩家會覺得奇怪的事
球具類的模組常常附一個 .bat 安裝檔,裡面會先
mkdir models\textures 再複製檔案,
很多人看不懂「不是丟進去就好了嗎」。
因為這一層的項目名稱本身就含資料夾, 重建它的工具得先把那個資料夾造出來,名稱才對得起來。 「工具是這樣運作的」屬於推論 —— 本站量到的是名稱帶路徑這個事實, 至於某個特定工具怎麼處理,本站沒有測。
怎麼自己看一遍
python mvp_fel_toolkit.py "你的遊戲資料夾" --big data\initstaz.big --entries
會印出:
封裝檔內共 1 個項目:
名稱 大小 內容是什麼(看檔頭認的)
----------------------------------------------------------------------------
initstay.big 1,212,663 QFS → BIGF(裡面又有 34 個項目)
----------------------------------------------------------------------------
合計 1,212,663 bytes
--entries 跟 --list 的差別:
--list 只看版面檔,--entries 每個項目都列,
而且會解開來看檔頭認出它是什麼。
工具在介面萬用工具那一課。
其他變體
- BIG4 —— EA 其他遊戲用的 little-endian 變體。MVP 2005 一個都沒有:剛安裝好的原版整套 1,191 個檔全部掃過檔頭,
BIG4命中 0 次;本站測試機那邊也是整棵資料夾逐檔掃(2026-09-05 掃到 4,974 個檔,這個數字會隨你自己放進去的模組變動),一樣 0 次。看到別的教學提到 BIG4,那是別的遊戲。 *hdr.big—— 名字看起來像,但要分兩種。原版 13 個*hdr.big裡,8 個根本不是 BIGF(bdcsthdr、chanthdr、hsfxhdr、plchthdr、rallyhdr、pahdr、pbphdr、stdmhdr),是另一種 EA 格式;另外 5 個是貨真價實的 BIGF 封裝檔(名字只有三個 ——pnamehdr、tnamehdr、stdnmhdr,前兩個各在兩個資料夾裡出現一次,裡面裝的是.hdr項目)。一樣是那句老話:看檔頭,不要看檔名。
📌 重點整理
- 一句話
.big是 EA 自家的封裝格式,一個 16 位元組檔頭加一份目錄就讀得完,真正會毀檔的是寫回去,只能把新資料接到檔尾,整包重新打包會靜靜地丟掉東西。 - 同一個檔頭混著兩種位元組順序 總大小是 little-endian,項目數與目錄結束位置是 big-endian。
misc.big的第 4 個位元組起是7E 03 00 00,當 big-endian 讀是 2,114,125,824(2 GB,明顯錯誤),當 little-endian 讀是 894,正是該檔真實大小。 - 最重要的一個數字 測試機的
ingame.big有 2,665,562 個位元組,47 個項目加起來只有 195,999,剩下 2,468,068(92.59%)目錄根本不指向。用重新打包的方式存檔,實測會變成 198,071 個位元組,47 個項目一個不少、內容檢查全過、不會有任何錯誤訊息。正確做法只有三件事:資料接到檔尾、改目錄那 8 個位元組、改檔頭那 4 個位元組。 - 訂正一:
+0x0C不是「資料起始位置」。它是目錄區結束之後的位置(檔頭 + 目錄 + 8 個位元組尾標),原版 207 個封裝檔 207/207 全中;但它不等於第一筆資料真正的位置,只有 18/207 相同,其餘中間隔著填充,最多差 119。要找資料請讀目錄裡那一筆自己的 offset。 - 訂正二:little-endian 不是通則。這台機器當時 295 個封裝檔裡 288 個 little-endian、7 個 big-endian、0 個兩種都對不上(2026-09-05 那個資料夾又被動過之後重量是 384 個裡 374/10/0,結論沒變),而那 7 個的時間戳全部落在 2012 年以後;出廠期那 146 個檔一個 big-endian 都沒有(2026-09-05 重跑是出廠期 175 個、2012 年以後 199/10,一樣掛零)。同一台機器上留著同一個檔名的
dodgnite.big,封存原廠球場那份是 2004-12-24、little-endian,遊戲現在用的球場模組那份是 2013-10-19、big-endian,同樣 88 個項目只有這一欄不同。寫回去之前先讀一次,原檔是哪一種就照哪一種寫。 - 訂正三:本站自己混用過兩種孤兒算法。同一個檔出現過 2,468,068 與 2,469,563 兩個數字,差的 1,495 正好是
16 + 1,479(檔頭與目錄算不算進孤兒),已統一成「不算」那個定義;站上另一處寫的原版孤兒 0.9% 也換算成 0.04%。另外原本寫的「撈得出 156 種舊版畫面代號、現行只有一個HUDLEFT」:156 只是口徑不同(整片沒被指向的位元組一起掃),讀得出整段文字的那 13 個區塊是 143 種、每一種現行版本都還在,錯的是「舊版」兩個字;而fes_hudleft.fel有 26 個。 - 前端封裝檔頭尾各有一個 1 位元組的書擋 目錄第一筆與最後一筆同名、各 1 個位元組,最後那筆的位置正好等於檔長減一,17 個封裝檔零例外,官方英文版與繁中版相同。拿檔名當鍵建字典會靜靜地少一筆,本站量
bkgnds.big先得到 106、跟先前記的 107 差一個,追下去才發現。球場那 87 個封裝檔沒有這個結構,所以它是前端的慣例不是格式規定。 - 本站沒驗的
initstaz/initprgz這兩個「封裝檔裡面還有封裝檔」為什麼要這樣包,檔案裡沒有寫;而「重建工具得先把資料夾造出來,名稱才對得起來」是推論,本站量到的只有內層 45 個項目名稱全部帶路徑這個事實,某個特定工具怎麼處理並沒有測。
接下來
- QFS / RefPack 壓縮 —— 封裝檔裡的項目常常還包一層壓縮
- FEL 版面檔 —— 解開壓縮後的介面腳本
- 實戰:換賽程年份 —— 用到本頁全部知識的完整流程