速查 › 語音檔頭的 16 個位元組
語音檔頭的 16 個位元組
2008 年,遊戲基地精華區有一篇〈語音檔製做法 新版〉, 把自製球員語音的流程一步一步寫下來。最後那一步是 「用 WinHex 開啟這 3 個檔,在第 2 欄處填 1E,第 3 欄處填 00」。 那是整篇最難照做、也最沒解釋的一步。本站把兩份原版裡全部 6,995 個檔頭拆開來數, 把那 16 個位元組每一格是什麼弄清楚了。
先說本站對那篇教學的態度
那篇文章記錄的流程是對的,而且在 2008 年沒有任何工具的情況下, 有人願意把「先轉 wav、剪接、再轉 asf、改副檔名、手動改檔頭」寫成十幾個步驟公開出來, 這件事本身就是這個社群能存在的原因。
本站沒有轉載它,也不會提供那些工具。這一頁做的是另一件事: 把它沒解釋的那一步,用可以重跑的量測補上。
原文標題〈語音檔製做法 新版〉,遊戲基地「美國職棒大聯盟系列」精華區 ﹥ 模組製作。 本站不轉載全文、不提供原檔,只逐字引用其中兩句以便對照。
第一個坑:檔頭跟聲音不住在同一個地方
聲音本體是 .dat,檔頭是 .hdr,兩個一定成對。
但它們在兩棵不同的資料夾樹裡:
| 是什麼 | 路徑 | 原版項目數 |
|---|---|---|
| 聲音本體 | data\audio\cd\spch_pbp\pnamedat.big | 4,580 |
| 檔頭 | data\audio\spch_pbp\pnamehdr.big | 4,580 |
差別只在中間那個 cd 資料夾。兩邊的檔名集合完全相同
——「少做任何一個都會導致遊戲跳出」這句提醒,說的就是這件事。
16 個位元組長什麼樣
先修一句:不是每個檔頭都 16 個位元組
這一頁的標題寫「16 個位元組」,那對球員名那一庫是對的 (4,580 個裡 4,565 個是 16,另外 15 個是 20)。但其他幾庫完全不是:
| 封裝檔 | 是什麼 | 檔頭大小 |
|---|---|---|
| spch_pbp\pnamehdr.big | 播報員唸球員名 | 16(4,565)/20(15) |
| spch_pbp\stdnmhdr.big | 播報員的固定句 | 16 ~ 36,六種 |
| spch_pbp\tnamehdr.big | 播報員唸隊名 | 72 ~ 148,九種 |
| spch_pa\pnamehdr.big | 球場廣播唸球員名 | 32,2,106 個全部一樣 |
| spch_pa\tnamehdr.big | 球場廣播唸隊名 | 28(60)/32(31) |
但下面那兩個欄位(編號、長度)的位置在五庫都一樣 —— 6,995 次比對全部成立,那才是這一頁真正在講的事。
「多出來的部分」現在解開了一半
本站原本寫「多出來的部分接在後面,沒有解讀」。這次把 6,995 個檔頭的 第 4 與第 5 個位元組逐一列出來,兩件事很清楚:
一、第 4 個位元組在同一個庫裡是常數。 它不隨檔案變,只隨「你在哪一個庫」變:
| 語音庫 | 項目數 | 位元組 4 | 檔頭長度與位元組 5 的關係 |
|---|---|---|---|
| spch_pbp\tnamehdr | 124 | 2 | 長度 = 4 × 位元組5 + 20 124 / 124 零例外 |
| spch_pa\tnamehdr | 91 | 1 | 長度 = 4 × 位元組5 + 12 91 / 91 零例外 |
| spch_pa\pnamehdr | 2,106 | 3 | 位元組5 全是 1、長度全是 32 —— 只有一種組合,推不出關係 |
| spch_pbp\pnamehdr | 4,580 | 0 | 檔頭長度不是位元組5 的簡單線性關係(pnamehdr 只有 16/20 兩種,stdnmhdr 有 16 ~ 36 共六種)
未解 |
| spch_pbp\stdnmhdr | 94 | 0 或 1 |
二、兩個庫的長度可以完全算出來。 那兩條線性關係各自零例外,而且常數不一樣(+20 與 +12), 代表不同的庫用不同的檔頭配置,不是同一套。
剩下三個庫還沒解。未解 但比原本的「多出來的部分沒有解讀」精確多了。
這一段是怎麼寫出來的(值得記一下)
本站跑了一輪內部覆驗,其中一份回報說這一整段解開了,
還給了一條通用公式(長度 = 12 + (2 + 位元組4) × 位元組5),
並宣稱「13,990 / 13,990 零例外」。
本站照著跑了一遍:那條公式在 6,995 個檔裡只成立 56 個。
但那份回報不是全錯 —— 它指出的方向(第 4、第 5 個位元組)是對的,
而且它提到的 tnamehdr 那條「4 × 段數 + 20」完全正確。
錯的是把一個庫成立的規律寫成五個庫的通則。
上面那張表是本站自己重量的,不是照抄的。 拿到一份漂亮的報告,第一件事是自己跑一次。
原版 0003a.hdr(球員名那一庫,16 個位元組),整份就這麼長:
位元組 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
E1 36 03 00 00 01 00 00 0B 00 00 00 00 00 70 70
└─┬─┘ └─┬─┘ └─┬─┘
類別 編號 3 區塊數減一 11
| 位元組 | 是什麼 | 怎麼驗的 |
|---|---|---|
| 0–1 | 類別標記。哪一種語音就是哪一組固定值 | 同一類的檔全部一樣,零例外(見下表) |
| 2–3 | 編號(16 位元、低位在前)。就是檔名前面那個數字 | 6,995 / 6,995 相符 |
| 4–7 | 幾乎全是固定值。未解 | 位元組 6、7 在全部 6,995 個檔頭裡都是 00;位元組 4 是各庫自己的常數(這一庫 4,580 個全是 00,其他庫見上表) |
| 8–9 | 長度,單位是 256 位元組的區塊,而且要減一 | 6,995 / 6,995 相符 |
| 10–15 | 其餘。未解 | 位元組 10–13 在全部檔案裡都是 00 |
那個「減一」不是筆誤
原版全部 6,995 個聲音檔,長度都是 256 的整數倍,一個例外都沒有。 而檔頭第 8–9 個位元組寫的是「區塊數減一」。
上面那個例子:檔頭寫 0B(11),聲音檔實際 3,072 位元組 = 12 × 256。
11 + 1 = 12。
驗算方式:(第 8–9 位元組的值 + 1) × 256 == .dat 的長度。
兩份原版(英文版與 EA 官方繁中版)各 6,995 個檔,合計 13,990 次比對全部成立。
⚠️ 一個誠實的但書:本站那份繁中版備份
有 45 個位元組的損壞,
而受影響的 20 個檔裡包含 spch_pbp\tnamedat.big(隊名語音)。
這一頁的公式算的是檔案長度,不是內容,
而位元組層級的損壞不會改變長度 —— 所以那 124 筆不受影響,
兩份備份也給出完全一樣的結果。
但範圍還是講清楚比較好。
類別標記對照
| 位元組 0–1 | 是哪一種 | 原版數量 | 封裝檔 |
|---|---|---|---|
| E1 36 | 球員語音 a(球評唸姓氏) | 1,462 | spch_pbp\pnamehdr.big |
| E3 36 | 球員語音 c(主播唸全名) | 1,486 | |
| E5 36 | 球員語音 d(主播唸姓氏) | 1,632 | |
| 9A 4A | 隊名 | 124 | spch_pbp\tnamehdr.big |
| 2B 3F | 固定句(第一種) | 56 | spch_pbp\stdnmhdr.big |
| 80 49 | 固定句(第二種) | 38 | |
| 10 20 | 球場廣播的球員名 | 2,106 | spch_pa\pnamehdr.big |
| 11 20 | 球場廣播的隊名 | 91 | spch_pa\tnamehdr.big |
a / c / d 這三個尾碼,光看檔頭第 0 個位元組就知道是哪一個
—— E1、E3、E5,4,580 個檔零例外。
括號裡那三個名字不是本頁量的,是
球員語音那一頁用
spch_pbp.txt 的類別編號集合逐一比對出來的(三組集合完全相同,
再加一道音檔長度佐證)。三個數量跟那一頁列的一模一樣,確定是同一組資料。
那句「第 2 欄、第 3 欄」差了一格
原文教的是:編號 30 換成十六進位是 001E,
然後「在第 2 欄處填 1E,第 3 欄處填 00」。
本站量到的是:編號在第 2 和第 3 個位元組(從 0 數起), 也就是十六進位編輯器裡第 3 欄和第 4 欄。
| 把編號放在哪 | 6,995 個檔裡對幾個 |
|---|---|
| 第 0–1 個位元組 | 0 |
| 第 1–2 個位元組(照原文字面) | 0 |
| 第 2–3 個位元組 | 6,995 |
| 第 3–4 個位元組 | 0 |
這很可能只是「欄」從 1 還是從 0 數起的差別,十八年前也沒有人能拿 6,995 個檔去對。 照原文字面做會把編號寫到左邊一格,而左邊那一格是類別標記 —— 改壞它,遊戲會拿這個檔當成別種語音。
不是每個球員都有三個檔
原版 pnamedat.big 裡有 1,673 個編號,
但三個尾碼齊全的只有 1,255 個:
| 有哪幾個 | 編號數 |
|---|---|
a + c + d(齊全) | 1,255 |
c + d | 191 |
a + d | 166 |
a + c | 40 |
只有 d | 20 |
只有 a | 1 |
EA 自己出貨的東西就有四分之一是不齊的。
所以「一定要三個都做」不是遊戲的要求,
真正的要求是你做了幾個 .dat,就要有對應的幾個 .hdr
—— 那篇教學的下一句其實也是這樣寫的。
社群做的語音有一半不遵守這條規則,而遊戲照跑
本站測試機(裝過台灣模組)的 pnamedat.big 有 12,450 個配對,
但其中只有 6,253 個的長度是 256 的整數倍
—— 剩下那 6,197 個連套用這條公式的前提都不成立。
而長度真的對齊的那 6,253 個裡,有 6,176 個(98.8%)符合公式。
換句話說:當年的社群工具產出的檔案不照 EA 的規矩,而這台機器一直是能玩的。
本站沒有去驗遊戲到底有沒有讀這一欄,也沒有做「故意寫錯看會不會壞」的實驗。 量到的就是「這台有 6,197 個不對齊的檔,而它是本站所有教學的測試機」這件事本身。 未驗
看四個位元組就知道這個語音不是 EA 錄的
上面講的是 .hdr。而聲音本體 .dat 的前 12 個位元組
也藏著一件很好用的事:
原版 0003a.dat 的開頭
53 43 48 6C 1C 00 00 00 50 54 00 00 06 01 65 FD
S C H l ← 長度 → P T \0 \0
本站測試機 0001a.dat 的開頭
53 43 48 6C 20 00 00 00 47 53 54 52 01 00 00 00
S C H l ← 長度 → G S T R
第 8 到 11 個位元組是「這個檔是哪一套工具做的」。實測:
| 哪一批檔案 | 數量 | PT\0\0 | GSTR | 其他 |
|---|---|---|---|---|
| 剛安裝好的原版全庫 | 4,580 | 4,580 | 0 | 0 |
| 測試機裡跟原版逐位元組相同的 | 3,692 | 3,692 | 0 | 0 |
| 測試機裡原版沒有、後來加的 | 7,988 | 1,680 | 5,778 | 530 |
所以這是一條單向的判定
看到 GSTR → 一定不是這兩份 2005 原版留下來的。
兩份原版全庫零個 GSTR,而測試機裡跟原版逐位元組相同的 3,692 個也零個。
但反過來不成立。看到 PT\0\0 不代表是原廠的
—— 後來加的 7,988 個裡就有 1,680 個是 PT\0\0。
為什麼會有兩套,本站不知道 —— 能量到的只有「原版清一色一種、後來加的混著兩種」。
未解
另外測試機裡還有 1 個檔開頭根本是 RIFF/WAVE,
也就是一個沒轉檔就丟進來的 Windows 音效檔。
另一個 1.17 MB 的純文字對照表
data\audio\spch_pbp\spch_pbp.txt,31,902 行,
裡面是原始錄音的檔名與它在資料裡的位置:
1000ms_40
1000ms.wav 0
1100ms.wav 3840
800ms.wav 7936
900ms.wav 11264
100ms_40
100ms.wav 0
沒有縮排的是群組名,縮排的是「檔名 + 從群組開頭算起的位移」。 每個群組的位移都從 0 重新起算。
這份表把 EA 當年錄音室裡的檔名留了下來
—— 1000ms.wav、800ms.wav 這種名字看得出是停頓長度。
本站沒有把這張表跟實際音訊對起來,也沒有解讀群組名的命名規則。 未解
順帶:有一個 212 MB 的語音檔完全不照這套規矩
data\audio\cd\spch_pbp\pbpdat.big 是整個遊戲最大的音訊檔
—— 212,219,648 個位元組,播報員的所有講評都在裡面。
它不是封裝檔。開頭四個位元組不是 BIGF,是 SCHl
—— 也就是說整個檔案就是一長串音訊串流接在一起,沒有目錄。
那怎麼找得到某一段?索引是一個純文字檔,
就在旁邊的 data\audio\spch_pbp\pbpdat.off:
pbpdat.off 前三行
0,15360,
15360,7168,
22528,10752,
每一行是「從第幾個位元組開始,長度多少」。全檔 2,056 行,
把所有長度加起來是 212,219,648 ——
剛好等於 pbpdat.big 的大小,一個位元組不多不少。
所以 EA 在同一個遊戲裡用了兩套索引做法:
大部分東西用 BIGF 把目錄放在檔頭,
而這個 212 MB 的檔把目錄拆出來放成一個純文字檔。
為什麼要這樣做,本站不知道。未解
一個猜得到但沒證據的方向是「這個檔太大,不想為了讀目錄先載入檔頭」——
本站沒有辦法驗證,所以只記錄現象。
怎麼自己重跑一次
不需要本站的工具。用 FEL 萬用工具 或任何能讀 BIG 封裝檔的東西,把兩個封裝檔的目錄列出來,然後對每一組同名的檔案算:
# 檔頭第 2-3 個位元組(低位在前)應該等於檔名前面的數字
編號 = 檔頭[2] + 檔頭[3] * 256
# 檔頭第 8-9 個位元組(低位在前)加一,乘 256,應該等於聲音檔的長度
長度 = (檔頭[8] + 檔頭[9] * 256 + 1) * 256
本站兩條都跑過 13,990 次(兩份原版 × 6,995 個檔),沒有一次不成立。 你手上那份如果裝過模組,數字會不一樣 —— 那正是上面那張卡在講的事。
📌 重點整理
- 一句話 2008 年那篇語音教學最難照做、也最沒解釋的一步是「用 WinHex 手改檔頭」,本站把兩份剛安裝好的原版各 6,995 個檔頭拆開來數,把那幾個位元組每一格是什麼弄清楚了。
- 兩條你可以自己重跑的公式 編號在第 2 到 3 個位元組(低位在前),長度在第 8 到 9 個位元組,單位是 256 位元組的區塊而且要減一,所以
(值 + 1) × 256 = .dat 的長度。兩條各 6,995/6,995 相符,兩份原版合計 13,990 次比對全部成立;原版全部 6,995 個聲音檔的長度都是 256 的整數倍,一個例外都沒有。 - 原文那句「第 2 欄、第 3 欄」差了一格 本站量到編號在第 2、3 個位元組(從 0 數起),也就是十六進位編輯器裡的第 3、4 欄。四種放法只有這一種對得上 6,995 個檔,其餘三種都是 0。照字面做會把編號寫到左邊一格,而那一格是類別標記,改壞它遊戲會把這個檔當成別種語音。
- 先修自己的標題:不是每個檔頭都 16 個位元組。那對球員名那一庫成立(4,580 個裡 4,565 個是 16、另外 15 個是 20),但播報員唸隊名那庫是 72 到 148 共九種、球場廣播唸球員名那庫 2,106 個全部是 32。不變的是上面那兩個欄位的位置,五個庫都一樣。
- 拿到一份漂亮的報告,第一件事是自己跑一次 一輪內部覆驗回報說這段全解了,給了一條通用公式並宣稱「13,990/13,990 零例外」,本站照著跑只成立 56 個。它不是全錯,方向(第 4、第 5 個位元組)對,
tnamehdr那條「長度 = 4 × 位元組5 + 20」124/124 也完全正確,錯在把一個庫成立的規律寫成五個庫的通則。 - EA 自己出貨的東西就有四分之一不齊 原版 1,673 個編號裡三個尾碼齊全的只有 1,255 個,其餘是
c+d191、a+d166、a+c40、只有d20、只有a1。所以「一定要三個都做」不是遊戲的要求,真正的要求是你做了幾個.dat就要有對應的幾個.hdr,而那兩種檔住在兩棵不同的資料夾樹裡,差別只在中間那個cd。 - 看四個位元組就知道這個語音不是 EA 錄的,但只能單向判定
.dat第 8 到 11 個位元組是工具標記,剛安裝好的原版 4,580 個全部是PT\0\0、零個GSTR,測試機裡跟原版逐位元組相同的 3,692 個也一樣。但看到PT\0\0不代表是原廠的,後來加的 7,988 個裡就有 1,680 個是。 - 本站沒驗的 檔頭第 4 到 7、10 到 15 個位元組大部分未解,五個庫裡還有三個庫的長度算不出來;為什麼會有
PT\0\0與GSTR兩套工具標記不知道;那個 31,902 行的spch_pbp.txt沒有跟實際音訊對起來、群組命名規則也沒解讀;212 MB 的pbpdat.big為什麼把目錄拆成一個純文字檔(2,056 行,長度加總 212,219,648 剛好等於檔案大小)本站不知道,只記錄現象。最誠實的一條:測試機那 12,450 個配對裡有 6,197 個長度根本不是 256 的倍數,連套用公式的前提都不成立,而這台機器一直是能玩的,本站沒有去驗遊戲到底有沒有讀這一欄,也沒做故意寫錯的實驗。
接下來
- 播報員為什麼不唸新球員的名字 —— 這件事的使用者版本
- 播報音訊系統 —— 整個音訊區的結構
- 兩套球員編號 ——
audioid怎麼對到這些檔名 - BIG 封裝檔格式 —— 怎麼把這些檔案列出來
- 原版與這台的對照 —— 為什麼本站每個數字都要標是哪一份