速查 › 封裝格式

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 個封裝檔逐一驗:

要找資料,請讀目錄裡那一筆自己的 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 710.04%
ingame.big 本站測試機(裝過很多模組)2,665,562 2,468,06892.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 個項目都還在、內容檢查也全過、不會有任何錯誤訊息 —— 問題可能等到遊戲跑進某個特定畫面才爆出來。

正確做法:接到檔尾

不要重排任何東西,只做三件事:

原始資料區一個位元組都不動。檔案會變大一點,但什麼都不會遺失。

我們改過這一段:那個「little-endian」不是通則

這頁上線時,檔頭第 4 個位元組起的「檔案總大小」直接標成 little-endian。 後來把這台機器上的封裝檔都量過一次(當時 295 個真的是 BIGF,不含使用者自己複製的那份 data 備份;同一批裡副檔名是 .big 的有 312 個,差的那 17 個檔頭根本不是 BIGF,見本頁最後一節),結果是:

檔頭那一欄的位元組順序檔數哪些
little-endian288絕大多數,包含本站教學動到的 ingame.bigschedule.big
big-endian7 models.bigportrait.bigpnamedat.bigpnamehdr.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-endianbig-endian
2004-2005(遊戲出廠那兩年) 1460
2012 年以後(社群改過的)1427

出廠期的檔案沒有半個是 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.dll2015 年的 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.fel26 個HUDLEFT 加上 HUDLEFT1HUDLEFT25), 不是一個 —— 這一點與本站 FEL 那頁原本就寫的數字相符,是這裡寫錯了。

每個前端封裝檔的頭尾各有一個 1 位元組的標記(2026-08-28 量的)

把剛安裝好的原版 data\frontend\ 底下每個裝圖片的封裝檔攤開來數, 會看到一件一開始像是壞掉的事:目錄裡有一個名字出現兩次

封裝檔重複的名字在目錄的第幾筆兩份的長度
portrait.biga2653.fsh第 0 筆 與 第 2,392 筆(共 2,393)各 1 byte
uniforms.biga022.fsh第 0 筆 與 第 556 筆(共 557)各 1 byte
stadiums.biga1a01.fsh第 0 筆 與 第 92 筆(共 93)各 1 byte
bkgnds.biga22bd.fsh第 0 筆 與 第 106 筆(共 107)各 1 byte
…另外 13 個都是 a 開頭同樣是第一筆與最後一筆各 1 byte

17 個封裝檔,17 個都是這樣,零例外,而且官方英文版與官方繁體中文版完全相同。

它們是一對書擋

所以它們標的是資料區的頭與尾。名字也不是隨機的: aaw…awards.bigabi…bpitems.bigacp…coopplyr.big —— 字首對應封裝檔自己的縮寫

⚠️ 寫工具的人一定要知道這件事

球場那些封裝檔沒有這個結構(本站量的 87 個一個都沒有), 所以這是前端封裝檔的慣例,不是 BIGF 格式本身的規定。

封裝檔裡面還有封裝檔

有兩個檔解開之後,裡面裝的是另一個封裝檔

外層外層大小裡面唯一的項目 解壓後內層項目數
data\initstaz.big1,212,711 initstay.big3,082,32034
data\initprgz.big107,019 initpurge.big332,70411

兩個都是外層只裝一個項目,那個項目是 QFS 壓縮的,解開就是一個完整的 BIGF。 外層檔名結尾是 z,內層是原本的名字 —— initstazinitstayinitprgzinitpurge為什麼要這樣包,檔案裡沒有寫

只有在這一層,項目名稱才會帶資料夾

本站把原版全部 207 個真的是 BIGF 的封裝檔目錄讀過一遍, 共 29,816 個項目:

項目名稱長什麼樣幾個
單純的檔名(ball.fsh2118.fsh29,816
帶資料夾路徑(models/ball.o0

可是拆開那兩個內層之後,45 個項目全部帶路徑:

initstay.big 的前幾個項目
models/bat.o          12,768
models/brknbat.o      17,536
models/ball.o          5,680
models/hibody.ord    786,336
misc/

內層前置資料夾:initstaymodels 33 個、misc 1 個; initpurgemodels 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 每個項目都列, 而且會解開來看檔頭認出它是什麼。 工具在介面萬用工具那一課。

其他變體

📌 重點整理

接下來