ppt檔案屬於複合文檔結構,下面講解一下複合文檔的結構。
倉庫與流
複合文檔的原理就像一個檔案系統(檔案系統:如FAT與NTFS)。複合文檔將數據分成許多流(Streams),這些流又存儲在不同的倉庫(Storages)里。將複合文檔想像成你的D糟,D糟用的是NTFS(NT File System)格式,流就相當於D糟里的檔案,倉庫就相當於D糟里的資料夾。
流和倉庫的命名規則與檔案系統相似,同一個倉庫下的流及倉庫不能重名,不同倉庫下可以有同名的流。每個複合文檔都有一個根倉庫(root storage)。
扇區與扇區鏈
扇區與扇區標識
所有的流又分成更小的數據塊,叫做數據扇區(sectors)。Sectors 可能包含控制數據或用戶數據。
整個檔案由一個頭(Header)結構以及其後的所有Sectors組成。Sectors的大小在頭中確定,且每個Sectors的大小都相同。
以下為示意圖:
HEADER
SECTOR 0
SECTOR 1
SECTOR 2
SECTOR 3
SECTOR 4
SECTOR 5
SECTOR 6
┆
Sectors 簡單的以其在檔案中的順序列舉,一個扇區的索引(從0開始)叫做扇區標識(SID:sector identifier)。SID是一個有符號的32位的整型值。
如果一個SID的值非負,就表示真正存在的那個Sector;如果為負,就表示特殊的含義。下表給出有效的特殊SID:
SID Name Meaning
–1 Free SID 空閒sector,可存在於檔案中,但不是任何流的組成部分。
–2 End Of Chain SID SID鏈的結束標記 (見2.2節)
–3 SAT SID 此Sector用於存放扇區配置表(SAT)(見4.2節)
–4 MSAT SID 此Sector用於存放主扇區配置表(MSAT)(見4.1節)
扇區鏈與扇區標識鏈
用於存儲流數據的所有Sectors的列表叫做扇區鏈(Sector Chain)。這些Sectors可以是無序的。因此用於指定一個流的Sectors的順序的SID數組就稱為SID chain。一個SID chain總是以End Of Chain SID(-2)為結束標記。
流的SID鏈是通過扇區配置表構建的(見4.2節),但短流和以下兩種內部流除外:
1.主扇區配置表,其從自身構建SID鏈(每個扇區包含下一個扇區的SID)。
2.扇區配置表,其通過主扇區配置表構建SID鏈。
複合文檔頭
複合文檔頭的內容
複合文檔頭在檔案的開始,且其大小必定為512位元組。這意味著第一個Sector的開始相對檔案的偏移量為512位元組 。
複合文檔頭的結構如下:
Offset Size Contents
0 8 複合文檔檔案標識:D0H CFH 11H E0H A1H B1H 1AH E1H
8 16 此檔案的唯一標識(不重要, 可全部為0)
24 2 檔案格式修訂號 (一般為003EH)
26 2 檔案格式版本號(一般為0003H)
28 2 位元組順序規則標識(見3.2)::FEH FFH = Little-Endian FFH FEH = Big-Endian
30 2 複合文檔中sector的大小(ssz),以2的冪形式存儲, sector實際大小為s_size=2ssz 位元組
(一般為9即512位元組, 最小值7即128位元組)
32 2 short-sector的大小(見5.1),以2的冪形式存儲, short-sector實際大
小為s_s_size = 2sssz 位元組(一般為6即64位元組,最大為sector的大小)
34 10 Not used
44 4 用於存放扇區配置表(SAT)的sector總數
48 4 用於存放目錄流的第一個sector的SID (見6)
52 4 Not used
56 4 標準流的最小大小(一般為4096 bytes), 小於此值的流即為短流。
60 4 用於存放短扇區配置表(SSAT)的第一個sector的SID (見5.2), 或為–2 (End Of Chain SID)如不存在。
64 4 用於存放短扇區配置表(SSAT)的sector總數
68 4 用於存放主扇區配置表(MSAT)的第一個sector的SID (見4.1), 或為–2 (End Of Chain SID) 若無附加的sectors。
72 4 用於存放主扇區配置表(MSAT)的sector總數
76 436 存放主扇區配置表(MSAT)的第一部分,包含109個SID。
位元組順序(Byte Order)
檔案數據的二進制存儲有兩種方法Little-Endian 和 Big-Endian,但實際套用中只使用Little-Endian方法即:低位8位元組存放在地址的低位,高位8位元組存放在地址的高位。
例:一個32位的整數13579BDFH(轉為十進制即324508639),以Little-Endian存放為DFH 9BH 57H13H,以Big-Endian 存放為 13H 57H 9BH DFH。(H下標表示十六進制)
扇區偏移量
從頭中的信息可以計算出一個sector的偏移量(offset),公式為:
sec_pos(SID) = 512 + SID ? s_size = 512 + SID ? 2 ssz
例:ssz = 10 and SID = 5:
sec_pos(SID) = 512 + SID ? 2 ssz = 512 + 5 ? 210 = 512 + 5 ? 1024 = 5632.
扇區配置
主扇區配置表
主扇區配置表(MSAT:master sector allocation table)是一個SID數組,指明了所有用於存放扇區配置表(SAT:sector allocation table)的sector的SID。MSAT的大小(SID個數)就等於存放SAT的sector數,在頭中指明。
MSAT的前109個SID也存放於頭中,如果一個MSAT的SID數多餘109個,那么多出來的SID將存放於sector中,頭中已經指明了用於存放MSAT的第一個sector的SID。在用於存放MSAT的sector中的最後一個SID指向下一個用於存放MSAT的sector,如果沒有下一個則為End Of Chain SID(-2)。
存放MSAT的sector的內容:(s_size表示sector的大小)
Offset Size Contents
0 s_size-4 MSAT的(s_size-4) / 4個SID的數組
s_size-4 4 下一個用於存放MSAT的sector的SID,或-2(已為最後一個)
最後一個存放MSAT的sector可能未被完全填滿,空閒的地方將被填上Free SID(-1)。
例:一個複合文檔需要300個sector用於存放SAT,頭中指定sector的大小為512位元組,這說明一個sector可存放128個SID。MAST有300個SID,前109個放於頭中,其餘的191個將要占用2個sector來存放。此例假定第一個存放MSAT的sector為sector 1,則sector 1包含127個SID。第128個SID指向一個用於存放MSAT的sector,假定為sector 6,則sector 6包含剩下的64個SID(最後一個SID為-2,其他的值為-1)。
扇區配置表
扇區配置表(SAT:sector allocation table)是一個SID數組,包含所有用戶流(短流除外)和內部控制流(the short-stream container stream, 見5.1, the short-sector allocation table, 見5.2, and the directory, 見7)的SID鏈。SAT的大小(SID個數)就等於複合文檔中所存在的sector的個數。
SAT的建立就是通過按順序讀取MSAT中指定的sector中的內容。
存放SAT的sector的內容:(s_size表示sector的大小)
Offset Size Contents
0 s_size SAT的s_size / 4個SID的數組
當通過SAT為一個流創建SID鏈時,SAT數組的當前位置(array index)表示的就是當前的sector,而該位置存放的SID則指向下一個sector。
SAT可能在任意位置包含Free SID(-1),這些sector將不被流使用。如果該位置包含End Of Chain SID(-2)表示一個流的結束。如果sector用於存放SAT則為SAT SID(-3),同樣用於存放MSAT則為MSAT SID(-4)。
一個SID鏈的起點從用戶流的目錄入口(directory entry,見6.2節)或頭(內部控制流)或目錄流本身獲得。
短流
短流存放流
當一個流的大小小於指定的值(在頭中指定),就稱為短流(short-stream)。 短流並不是直接使用sector存放數據,而是內含在一種特殊的內部控制流——短流存放流(short-stream container stream)中。
短流存放流象其他的用戶流一樣:先從目錄中的根倉庫入口(root storage entry)獲得第一個使用的sector,其SID鏈從SAT中獲得。然後此流將其所占用的sectors分成short-sector,以便用來存放短流。此處也許較難理解,我們來打個比方:既然流組成符合文檔,而短流組成短流存放流,這兩者是相似的。把短流存放流當作複合文檔,那么短流對應流,short-sector對應sector,唯一的不同是複合文檔有一個頭結構,而短流存放流沒有。short-sector的大小在頭中已經指定,因此可根據SID計算short-sector相對於短流存放流的偏移量(offset)。公式為:
short_s_pos(SID) = SID ? short_s_size = SID ? 2 sssz
例:sssz = 6 and SID = 5:
short_s_pos(SID) = SID ? 2 sssz = 5 ? 26 = 5 ? 64 = 320.
5.2 短扇區配置表
短扇區配置表(SSAT:short-sector allocation table)是一個SID數組,包含所有短流的SID鏈。與SAT很相似。
用於存放SSAT的第一個sector的SID在頭中指定,其餘的SID鏈從SAT中獲得。
存放SSAT的sector的內容:(s_size表示sector的大小)
Offset Size Contents
0 s_size SSAT的s_size / 4個SID的數組
SSAT的用法與SAT類似,不同的是其SID鏈引用的是short-sector。
目錄
目錄結構
目錄(directory)是一種內部控制流,由一系列目錄入口(directory entry)組成。每一個目錄入口都指向複合文檔的一個倉庫或流。目錄入口以其在目錄流中出現的順序被列舉,一個以0開始的目錄入口索引稱為目錄入口標識(DID: directory entry identifier)。
如下圖所示:
DIRECTORY ENTRY 0
DIRECTORY ENTRY 1
DIRECTORY ENTRY 2
DIRECTORY ENTRY 3
...
目錄入口的位置不因其指向的倉庫或流的存在與否而改變。如果一個倉庫或流被刪除了,其相應的目錄入口就標記為空。在目錄的開始有一個特殊的目錄入口,叫做根倉庫入口(root storage entry),其指向根倉庫。
目錄將每個倉庫的直接成員(倉庫或流)放在一個獨立的紅黑樹(red-black tree)中。紅黑樹是一種樹狀的數據結構,本文僅簡單介紹一下,詳細情況請參考有關資料。
建構一個Red-Black tree的規則:
1. 每個節點(node)的顏色屬性不是紅就是黑。
2. 根節點一定是黑的。
3. 如果某個節點是紅的,那它的子節點一定是黑的。
4. 從根節點到每個葉節點的路徑(path)必須有相同數目的黑節點。
ex: B (用圖形來解說第4點,從根節點
/ \ 到最底層的node,你會發現每個
B B path都恰好有3個black node)
/ \ / \
B B R B
/ / \ / \
R B B R R
/ \
R R
注意並不總是執行上述規則。安全的方法是忽略節點的顏色。
1.根倉庫入口描述根倉庫,它不是任何倉庫入口的成員,因此無需構建紅黑樹。
2.根倉庫的所有直接成員(“Storage1”, “Storage2”, “Stream1”, “Stream2”, “Stream3”, 和 “Stream4”)將組成一棵紅黑樹,其根節點的DID存放於根倉庫入口中。
3.倉庫Storage1隻有一個成員Stream1,Stream1構成一棵紅黑樹,此樹只有一個節點。Storage1的目錄入口包含Stream1的DID。
4. 倉庫Storage2包含3個成員“Stream21”, “Stream22”, 和“Stream23”。這3個成員將構建一棵紅黑樹,其根節點的DID存放於Storage2的目錄入口中。
這種存放規則將導致每個目錄入口都包含3個DID:
1.在包含此目錄入口的紅黑樹中,此目錄入口的左節點的DID。
2.在包含此目錄入口的紅黑樹中,此目錄入口的右節點的DID。
3.若此目錄入口表示一個倉庫,則還包含此倉庫的直接成員所組成的另一顆紅黑樹的根節點的DID。
在構建紅黑樹的過程中,一個節點究竟作為左還是右,是通過比較其名字來判斷的。一個節點比另一個小是指其名字的長度更短,如長度一樣,則逐字元比較。
規定:左節點<根節點<右節點。
目錄入口
一個目錄入口的大小嚴格地為128位元組,計算其相對目錄流的偏移量的公式為:dir_entry_pos(DID) = DID ? 128。
目錄入口的內容:
Offset Size Contents
0 64 此入口的名字(字元數組), 一般為16位的Unicode字元,
以0結束。(因此最大長度為31個字元)
64 2 用於存放名字的區域的大小,包括結尾的0。
(如:一個名字右5個字元則此值為(5+1)?2 = 12)
66 1 入口類型: 00H = Empty 03H = LockBytes (unknown) 01H = User storage
04H = Property (unknown) 02H = User stream 05H = Root storage
67 1 此入口的節點顏色: 00H = Red 01H = Black
68 4 其左節點的DID (若此入口為一個user storage or stream) 若沒有左節點就為-1。
72 4 其右節點的DID (若此入口為一個user storage or stream), 若沒有右節點就為-1。
76 4 其成員紅黑樹的根節點的DID (若此入口為storage), 其他為-1。
80 16 唯一標識符(若為storage)(不重要, 可能全為0)
96 4 用戶標記(不重要, 可能全為0)
100 8 創建此入口的時間標記。大多數情況都不寫。
108 8 最後修改此入口的時間標記。大多數情況都不寫。
116 4 若此為流的入口,指定流的第一個sector或short-sector的SID,若此為根倉庫入口,指定短流存放流的第一個sector的SID,
其他情況,為0。 120 4 若此為流的入口,指定流的大小(位元組)若此為根倉庫入口,指定短流存放流的大小
(位元組)其他情況,為0。
124 4 Not used
時間標記(time stamp) 是一個符號的64位的整數,表示從1601-01-01 00:00:00開始的時間值。此值的單位為10-7秒。
當計算時間標記是要注意閏年。
例:時間標記值為01AE408B10149C00H
計算步驟 公式 結果
轉為十進制 t0 = 121,105,206,000,000,000
化成秒的餘數 rfrac = t0 mod 107 rfrac = 0
化成秒的整數 t1 = t0 / 107 t1 = 12,110,520,600
化成分的餘數 rsec = t1 mod 60 rsec = 0
化成秒的整數 t2 = t1 / 60 t2 = 201,842,010
化成小時的餘數 rmin = t2 mod 60 rmin = 30
化成小時的整數 t3 = t2 / 60 t3 = 3,364,033
化成天的餘數 rhour = t3 mod 24 rhour = 1
化成天的整數 t4 = t3 / 24 t4 = 140,168
距1601-01-01的整年 ryear = 1601 + t4含的年 ryear = 1601 + 383 = 1984
到1984年還剩的天數 t5 = t4 – (1601-01-01 t5 = 140,168 – 139,887 = 281
到1984-01-01的天數)
距1984-01-01的月數 rmonth = 1 + t5含的月數 rmonth = 1 + 9 = 10
到10月還剩的天數 t6 = t5 – (1984-01-01 t6 = 281 – 274 = 7
到1984-10-01的天數)
10月最終天數 rday = 1 + t6 rday = 1 + 7 = 8