速查 › 語音檔頭的 16 個位元組

語音檔頭的 16 個位元組

2008 年,遊戲基地精華區有一篇〈語音檔製做法 新版〉, 把自製球員語音的流程一步一步寫下來。最後那一步是 「用 WinHex 開啟這 3 個檔,在第 2 欄處填 1E,第 3 欄處填 00」。 那是整篇最難照做、也最沒解釋的一步。本站把兩份原版裡全部 6,995 個檔頭拆開來數, 把那 16 個位元組每一格是什麼弄清楚了。

先說本站對那篇教學的態度

那篇文章記錄的流程是對的,而且在 2008 年沒有任何工具的情況下, 有人願意把「先轉 wav、剪接、再轉 asf、改副檔名、手動改檔頭」寫成十幾個步驟公開出來, 這件事本身就是這個社群能存在的原因。

本站沒有轉載它,也不會提供那些工具。這一頁做的是另一件事: 把它沒解釋的那一步,用可以重跑的量測補上

原文標題〈語音檔製做法 新版〉,遊戲基地「美國職棒大聯盟系列」精華區 ﹥ 模組製作。 本站不轉載全文、不提供原檔,只逐字引用其中兩句以便對照。

第一個坑:檔頭跟聲音不住在同一個地方

聲音本體是 .dat,檔頭是 .hdr,兩個一定成對。 但它們在兩棵不同的資料夾樹裡:

是什麼路徑原版項目數
聲音本體data\audio\cd\spch_pbp\pnamedat.big4,580
檔頭data\audio\spch_pbp\pnamehdr.big4,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\tnamehdr1242 長度 = 4 × 位元組5 + 20 124 / 124 零例外
spch_pa\tnamehdr911 長度 = 4 × 位元組5 + 12 91 / 91 零例外
spch_pa\pnamehdr2,1063 位元組5 全是 1、長度全是 32 —— 只有一種組合,推不出關係
spch_pbp\pnamehdr4,5800 檔頭長度不是位元組5 的簡單線性關係pnamehdr 只有 16/20 兩種,stdnmhdr 有 16 ~ 36 共六種) 未解
spch_pbp\stdnmhdr940 或 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,462spch_pbp\pnamehdr.big
E3 36球員語音 c(主播唸全名)1,486
E5 36球員語音 d(主播唸姓氏)1,632
9A 4A隊名124spch_pbp\tnamehdr.big
2B 3F固定句(第一種)56spch_pbp\stdnmhdr.big
80 49固定句(第二種)38
10 20球場廣播的球員名2,106spch_pa\pnamehdr.big
11 20球場廣播的隊名91spch_pa\tnamehdr.big

a / c / d 這三個尾碼,光看檔頭第 0 個位元組就知道是哪一個 —— E1E3E5,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 + d191
a + d166
a + c40
只有 d20
只有 a1

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\0GSTR其他
剛安裝好的原版全庫4,580 4,58000
測試機裡跟原版逐位元組相同3,692 3,69200
測試機裡原版沒有、後來加的7,988 1,6805,778530

所以這是一條單向的判定

看到 GSTR → 一定不是這兩份 2005 原版留下來的。 兩份原版全庫零個 GSTR,而測試機裡跟原版逐位元組相同的 3,692 個也零個。

但反過來不成立。看到 PT\0\0 不代表是原廠的 —— 後來加的 7,988 個裡就有 1,680 個是 PT\0\0

為什麼會有兩套,本站不知道 —— 能量到的只有「原版清一色一種、後來加的混著兩種」。 未解 另外測試機裡還有 1 個檔開頭根本是 RIFFWAVE, 也就是一個沒轉檔就丟進來的 Windows 音效檔。

另一個 1.17 MB 的純文字對照表

data\audio\spch_pbp\spch_pbp.txt31,902 行, 裡面是原始錄音的檔名與它在資料裡的位置:

1000ms_40
1000ms.wav                       0
1100ms.wav                       3840
800ms.wav                        7936
900ms.wav                        11264
100ms_40
100ms.wav                        0

沒有縮排的是群組名,縮排的是「檔名 + 從群組開頭算起的位移」。 每個群組的位移都從 0 重新起算

這份表把 EA 當年錄音室裡的檔名留了下來 —— 1000ms.wav800ms.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 個檔),沒有一次不成立。 你手上那份如果裝過模組,數字會不一樣 —— 那正是上面那張卡在講的事。

📌 重點整理

接下來