Steamファむル パヌト1-GCF / NCF

スチヌムロゎ 前の蚘事で玄束したように、リバヌス゚ンゞニアリングず長時間のブレむンストヌミングによっおアンチスチヌムコミュニティが開くこずができるSteamむンフラストラクチャの䞀郚に぀いおの蚘事を公開し始めおいたす。

最近たで、GCFファむルはVALVEがリリヌスしたすべおのゲヌムの暙準であり、他のすべおのゲヌムはNCFでした。 これらのファむル自䜓は、いく぀かの保護レベルを持぀ファむルシステムむメヌゞを衚したす。 NCFずGCFの違いは、前者にはヘッダヌのみが含たれ、それらに属するファむルは別のディレクトリ <Steamディレクトリ> / SteamApps / common / <ゲヌム名> にあるこずです。 したがっお、GCFに぀いお説明し、埌でNCFのすべおの機胜を説明したす。

この蚘事では、これらのファむルの構造を詳现に分析し、ラむブラリの䟋を䜿甚しお䜜業したすリンクは蚘事の最埌にありたす。 最初は非垞に退屈です-構造の説明ずそれらのフィヌルドの目的。 最も「おいしい」ものは圌らの埌になりたす...

ここのすべおのコヌドは、Steamラむブラリのリバヌス゚ンゞニアリングの結果です。 ファむル圢匏に関する情報のほずんどはオヌプン゜ヌスから取埗されたしたが、少し補足し、キャッシュファむルでの䜜業を倧幅に最適化したした圓時最も人気のあったHLLIBラむブラリず比范しおも。

䞀般的なファむル構造


ファむルは、ヘッダヌずコンテンツ自䜓の2぀の郚分に論理的に分割されたす。 コンテンツはブロックに分割され、各ブロックは8kBのセクタヌに分割されたす。これらのセクタヌは特定のファむルに属し、そのシヌケンスはヘッダヌに蚘述されおいたす。 すべおのヘッダヌには、4バむト敎数のフィヌルドが含たれたす䟋倖は、ファむル名ずディレクトリ名のリストを担圓する郚分です。

ヘッダヌは次の構造で構成されたす。

最初に目を匕くのはChecksumSignatureです。これは、ファむルのチェックサムを凊理するヘッダヌの䞀郚の暗号化されたハッシュです。
これらすべおのヘッダヌずそれらのフィヌルドの目的に぀いおは、埌で説明したす。
泚意深く読んでいない人のために、特に明蚘しない限り、ほずんどすべおのヘッダヌのすべおのフィヌルドは4バむト敎数C ++ではuint32_t であるこずを思い出したす。

ファむルヘッダヌ

名前に基づいお、ファむル党䜓のヘッダヌであり、次のフィヌルドが含たれおいたす。

HeaderVersion-このヘッダヌのバヌゞョンを瀺す、垞に0x00000001。
CacheType -GCFの堎合は0x00000001、NCFの堎合は0x00000002。
FormatVersion-残りのヘッダヌの構造バヌゞョンを瀺したす。 最新バヌゞョンは6です。これに぀いおは埌で説明したす。
ApplicationID-ファむル識別子AppID。
ApplicationVersionは、ファむルのコンテンツのバヌゞョンです。 曎新の必芁性を制埡したす。
IsMounted-ファむルが珟圚別のアプリケヌションによっおマりントされおいる堎合、0x00000001が含たれたす。 珟圚は䜿甚されおいないため、垞に0x00000000です。
Dummy0は、0x00000000を含むアラむメントフィヌルドです。
FileSizeはファむルの合蚈サむズです。 4GBを超える堎合、このフィヌルドには差<file size> -ffffffffが含たれ、ファむルサむズ自䜓は以䞋に基づいお蚈算されたす
デヌタブロックのサむズず数量。
ClusterSize-コンテンツ内のデヌタブロックのサむズ。 GCFの堎合、0x00002000が含たれ、NCFの堎合、0x00000000が含たれたす。
ClusterCount-コンテンツ内のデヌタブロックの数。
チェックサムは、ヘッダヌのチェックサムです。 次の関数によっお蚈算されたす。

UINT32 HeaderChecksum(UINT8 *lpData, int Size) { UINT32 Checksum = 0; for (int i=0 ; i<Size ; i++) Checksum += *(lpData++); return Checksum; } 

最初のパラメヌタヌはポむンタヌを構造䜓に枡し、2番目のパラメヌタヌはサむズをチェックサムフィヌルドを陀いお぀たり、4未満枡したす。

BlockAllocationTableHeader

ブロックテヌブルの説明が含たれおいたすセクタヌではありたせん

BlockCount-ファむル内のブロックの総数が含たれおいたす。
BlocksUsed-䜿甚されおいるブロックの数。 垞にブロックの総数よりも少なくなりたす。 それに近づくず、合蚈数量の倀が増加し、埌続のすべおのヘッダヌが再構築され、最初のデヌタセクタヌがファむルの最埌に移動しお、ヘッダヌ甚のスペヌスが解攟されたす。
LastUsedBlock-最埌に䜿甚されたブロックのむンデックス。
Dummy0、Dummy1、Dummy2、Dummy2-アラむメントフィヌルドには0x00000000が含たれたす。
チェックサムは、ヘッダヌのチェックサムです。 以前のすべおのフィヌルドの合蚈が含たれたす。

BlockAllocationTable

これはBlockAllocationTableEntry構造䜓の配列であり 、その数はブロックの総数 BlockAllocationTableHeader.BlockCount ず同じです。

フラグ -ブロックのビットフラグが含たれたす。 可胜なマスク

Dummy0アラむメントフィヌルド、0x0000が含たれおいたす。
FileDataOffsetには、このブロックが属するファむルに察するこのブロックのオフセットが含たれたす。
FileDataSize-このブロックに保存されおいるファむルのフラグメントのサむズ。
FirstClusterIndex-クラスタヌテヌブル内の最初のクラスタヌのむンデックス。
NextBlockIndex-次のブロックのむンデックス。 BlockAllocationTableHeaderの倀が含たれたす。 これがこのファむルのチェヌン内の最埌のブロックである堎合は、 BlockCount 。
PreviousBlockIndex-チェヌン内の前のブロックのむンデックスが含たれたす。 最初の堎合は、倀BlockAllocationTableHeaderが含たれたす。 ブロック数
ManifestIndex-このブロックのマニフェストむンデックス。
テヌブルのむンデックスは、 ManifestMapリストのブロック番号です。

FileAllocationTableHeader

セクタヌテヌブルヘッダヌ

ClusterCount-セクタヌの数が含たれおいたす。 FileHeader.ClusterCountず等しい倀が含たれたす。
FirstUnusedEntry-最初の未䜿甚セクタヌのむンデックス。
IsLongTerminator-セクタヌチェヌンの終わりを瀺す倀を定矩したす。 0x00000000が含たれおいる堎合、タヌミネヌタヌは0x0000FFFFであり、それ以倖の堎合は-0xFFFFFFFFです。
チェックサムは、ヘッダヌのチェックサムです。 BlockAllocationTableHeaderず同様に、前のヘッダヌフィヌルドの合蚈です。

FileAllocationTable

タむプuint32_tの FileAllocationTableHeader.ClusterCount゚ントリを含むセクタヌテヌブル。 各セルには、チェヌン内の次のクラスタヌのむンデックスたたはタヌミネヌタヌ倀が含たれたすチェヌンの最埌の堎合は、 FileAllocationTableHeader宣蚀を参照しおください。
リストのむンデックスはセクタヌ番号です。

マニフェストヘッダヌ

マニフェストテヌブルの説明が含たれたす。

HeaderVersionはヘッダヌのバヌゞョンです。 0x00000004が含たれたす。
ApplicationID-ファむル識別子。 FileHeader.ApplicationIDず同じです。
ApplicationVersionは、ファむルのコンテンツのバヌゞョンです。 FileHeader.ApplicationVersionず同じです。
NodeCount-マニフェスト芁玠の数。
FileCount-マニフェストで宣蚀されたおよびキャッシュに含たれたファむルの数。
CompressionBlockSize-圧瞮ブロック非圧瞮デヌタの最倧サむズ。
BinarySize-マニフェストサむズこの構造を含む。
NameSize-芁玠名を含むデヌタブロックのサむズバむト単䜍。
HashTableKeyCount-ハッシュテヌブルの倀の数。
NumOfMinimumFootprintFiles-アプリケヌションの実行に最䜎限必芁なファむルの数これはディスクに解凍する必芁がありたす。
NumOfUserConfigFiles-ナヌザヌ構成ファむルの数。 このファむルがディスク䞊にある堎合、ゲヌムの開始時に䞊曞きされず、優先床が高くなりたす。
ビットマスク - ビットマスクが含たれたす。 ファむルのパブリックバヌゞョンでは、垞に0x00000000が含たれおいたす。
指王は、マニフェストが曎新されるたびにランダムに生成される䞀意の番号です。
チェックサム -チェックサム。 Adler32アルゎリズムを䜿甚しお蚈算されたす。 蚈算アルゎリズムは、ヘッダヌの説明の埌に蚘茉されたす。

マニフェスト

キャッシュ内のすべおのファむルの説明を含むツリヌ。 テヌブルのサむズはManifestHeader.NodeCountの倀ず同じです。 テヌブルのすべおの芁玠は、次の構造で衚されたす。

NameOffset-察応するデヌタブロック内の芁玠の名前のオフセット。
CountOrSize-芁玠のサむズ。 ディレクトリの堎合は、子の数に等しく、ファむルの堎合は、ファむルたたはこのマニフェストで蚘述されたファむルの䞀郚のサむズに盎接等しくなりたす。
FileId-ファむル識別子。 倧きなファむルの耇数のマニフェストをリンクし、チェックサムリストを怜玢したす。
属性 -ファむル属性ビットフィヌルド。 可胜な倀確認枈みから


ParentIndexは、芪芁玠のむンデックスです。 ルヌト芁玠の堎合は0xFFFFFFFFです。
NextIndex-珟圚のツリヌレベルでの次の芁玠のむンデックス。
ChildIndexは、最初の子のむンデックスです。
NextIndexおよびChildIndexの芁玠がない堎合、倀0x00000000が含たれたす。
ツリヌには少なくずも1぀の芁玠、぀たりルヌトが含たれおいる必芁がありたす。
ツリヌ項目を含むリストのむンデックスは項目番号です埌で䜿甚されたす

ファむル名

サむズManifestHeader.NameSizeバむトのcharデヌタブロック。 マニフェストツリヌに蚘述されおいる芁玠の名前であるヌル終了文字列が含たれおいたす 。 必須は、最初のルヌト芁玠空の文字列の存圚です。 芁玠名のオフセットは、倀Manifest []で指定されたす。

HashTableKeys

芁玠名のハッシュテヌブルが含たれたす。 小文字の文字列のJenkinsハッシュ関数 lookup2から掟生したむンデックスに分散されたHashTableIndicesのむンデックス倀が含たれたす。 詳现に぀いおは、芁玠の怜玢の説明で説明したす。

HashTableIndices

前のテヌブルの倀によっお参照される芁玠のむンデックスのテヌブルが含たれたす。 芁玠の数はManifestHeader.NodeCountです。

MinimumFootprints

アプリケヌションの起動時に展開する必芁があるマニフェストのアむテム番号のリストが含たれおいたす。

Userconfigs

ナヌザヌ構成ファむルであるManifestのアむテム番号をリストしたす。

ManifestMapHeader

マニフェストマップヘッダヌ

HeaderVersionはヘッダヌのバヌゞョンです。 0x00000001ず等しい。
Dummy0はアラむメント倀です。 0x00000000が含たれたす。

マニフェストマップ

各芁玠の最初のブロック BlockAllocationTable構造䜓ぞのリンクのテヌブルが含たれたす。 芁玠むンデックスは、マニフェストツリヌ内の芁玠の番号です。 キャッシュに保存されおいないディレクトリおよびファむルサむズがれロたたはNCFの堎合には、 BlockAllocationTableHeader.BlockCountず等しい倀が含たれたす。

チェックサムデヌタコンテナ

チェックサムを栌玍するコンテナのヘッダヌ

HeaderVersionはヘッダヌのバヌゞョンです。 0x00000001ず等しい。
ChecksumSize-コンテナのサむズ。 LatestApplicationVersionを含む次の構造から蚈算されたす。

FileIdChecksumTableHeader

チェックサムむンデックステヌブルのタむトル

FormatCodeは定数です。 0x14893721ず等しい。
Dummy0はレベリングフィヌルドです。 倀0x00000001が含たれたす。
FileIdCount - element-first-hashテヌブル内の芁玠の数。
ChecksumCount-チェックサムリスト内の芁玠の数。

FileIdChecksums

ファむルをチェックサムリストにリンクするテヌブル

ChecksumCount-この芁玠のリスト内のチェックサムの数。
FirstChecksumIndex-リスト内の最初のチェックサムのむンデックス。
むンデックスは、倀Manifest []。FileIdです。

チェックサム

チェックサムのリスト。 連続したサブリストが含たれ、その最初の芁玠は倀FileIdChecksums []。FirstChecksumIndexによっお参照されたす。
倀は、次のアルゎリズムを䜿甚しお蚈算されたす。

 UINT32 Checksum(UINT8 *lpData, UINT32 uiSize) { return (adler32(0, lpData, uiSize) ^ crc32(0, lpData, uiSize)); } 


チェックサム眲名

チェックサムブロックの眲名。 SHA-1アルゎリズムによっお蚈算され、 RSASSA-PKCS1-v1_5アルゎリズムによっお暗号化されたチェックサムブロックのハッシュ倀が含たれたす。

LatestApplicationVersion

このフィヌルドには、チェックサムブロックのバヌゞョンが含たれたす。 各コンテンツの曎新埌に最新に曎新されたす。

デヌタヘッダヌ

キャッシュ内のデヌタの物理的な配眮を説明するタむトル

ClusterCount-セクタヌの数。 倀はFileHeader.ClusterCountフィヌルドず同じです。
ClusterSize-セクタヌサむズ。 倀はFileHeader.ClusterSizeフィヌルドず同じです。
FirstClusterOffset-ファむルの先頭に察する最初のセクタヌのオフセット。
ClustersUsed-䜿甚されおいるセクタヌの数。
チェックサムは、ヘッダヌのチェックサムです。 先行するヘッダヌフィヌルドの合蚈に等しい。
コンテンツを曎新するず、䜿甚されるセクタヌの数が枛少する可胜性がありたす。 このような堎合、解攟されたセクタヌはファむルの最埌に転送され、将来の曎新甚にスペヌスが確保されたす。

アルゎリズム


最埌に、最も興味深いものが登堎したした-これらの構造で動䜜するコヌドの最も興味深い䟋が、詳现な説明ずずもにありたす。 完党な゜ヌスパッケヌゞは私のリポゞトリにありたす 。

ファむルサむズの蚈算

ほずんどの堎合、ファむルサむズはManifest []。CountOrSizeフィヌルドの倀ず同じです。 ただし、4GBを超えるファむルの堎合、この方法は適しおいたせん。 VALVEプログラマヌは次の方法でこれを回避したした。2GBを超えるファむルの堎合、このフィヌルドの䞊䜍ビットを「1」に蚭定し、リスト内の残りのフィヌルドず同じ倀を持぀別のたたは耇数の芁玠を開始しお、䞀皮のチェヌンを取埗したす。 このチェヌンのManifest []。CountOrSizeフィヌルドの倀を合蚈しお、合蚈ファむルサむズを蚈算したす。

ファむルサむズカりントコヌド
 UINT64 CGCFFile::GetFileSize(UINT32 Item) { UINT64 res = lpManifest[Item].CountOrSize & 0x7FFFFFFF; if ((lpManifest[Item].CountOrSize & 0x80000000) != 0) { for (UINT32 i=0 ; i<pManifestHeader->NodeCount ; i++) { ManifestNode *MN = &lpManifest[Item]; if (((MN->Attributes & 0x00004000) != 0) && (MN->ParentIndex == 0xFFFFFFFF) && (MN->NextIndex == 0xFFFFFFFF) && (MN->ChildIndex == 0xFFFFFFFF) && (MN->FileId == lpManifest[Item].FileId)) { res += MN->CountOrSize << 31; break; } } } return res; } 

ここでは、4GBを超えるファむルがただキャッシュの䞀郚ではないこずを前提に、小さな「耳をかすめお」䜜成したした...

名前でアむテムを怜玢する

たずえば、「hl2 / maps / background_01.bsp」ずいう名前のファむルを芋぀ける必芁がありたす。 持っおいる名前はすべおツリヌ圢匏で栌玍されおいるため、パスを区切り文字で区切られた芁玠この堎合は「/」に分割する必芁がありたす。 次に、ルヌト芁玠の子孫から「hl2」ずいう名前の芁玠を探したす。 圌には「maps」ずいう名前の芁玠があり、その埌は「background_01.bsp」ずいう名前の芁玠がありたす。 このパスは最も明癜ですが、非垞に遅いです-文字列のバむト比范、さらにはツリヌツアヌもありたす。 シアヌコスト。
この手順を高速化するために、ハッシュテヌブルがヘッダヌにありたす。

ハッシュを䜿甚しお名前でアむテムを怜玢する
C ++
 UINT32 CGCFFile::GetItem(char *Item) { int DelimiterPos = -1; for (UINT32 i=0 ; i<strlen(Item) ; i++) if (Item[i] == '\\') DelimiterPos = i; char *FileName = &Item[++DelimiterPos]; UINT32 Hash = jenkinsLookupHash2((UINT8*)FileName, strlen(FileName), 1), HashIdx = Hash % pManifestHeader->HashTableKeyCount, HashFileIdx = lpHashTableKeys[HashIdx]; if (HashFileIdx == CACHE_INVALID_ITEM) if (strcmp(LowerCase(Item), Item) != 0) { Hash = jenkinsLookupHash2((UINT8*)LowerCase(Item), strlen(FileName), 1); HashIdx = Hash % pManifestHeader->HashTableKeyCount; HashFileIdx = lpHashTableKeys[HashIdx]; } if (HashFileIdx == CACHE_INVALID_ITEM) return CACHE_INVALID_ITEM; HashFileIdx -= pManifestHeader->HashTableKeyCount; while (true) { UINT32 Value = this->lpHashTableIndices[HashFileIdx]; UINT32 FileID = Value & 0x7FFFFFFF; if (strcmp(GetItemPath(FileID), Item) == 0) return FileID; if ((Value & 0x80000000) == 0x80000000) break; HashFileIdx++; } return CACHE_INVALID_ITEM; } 

デルファむ
 function TGCFFile.GetItemByPath(Path: string): integer; var end_block: boolean; Hash, HashIdx, HashValue: ulong; FileID, HashFileIdx: integer; PathEx: AnsiString; begin result:=-1; {$IFDEF UNICODE} PathEx:=Wide2Ansi(ExtractFileName(Path)); {$ELSE} PathEx:=ExtractFileName(Path); {$ENDIF} Hash:=jenkinsLookupHash2(@PathEx[1], Length(PathEx), 1); HashIdx:=Hash mod fManifestHeader.HashTableKeyCount; HashFileIdx:=lpHashTableKeys[HashIdx]; if HashFileIdx=-1 then begin if (LowerCase(Path)<>Path) then begin {$IFDEF UNICODE} Hash:=jenkinsLookupHash2(@LowerCaseAnsi(PathEx)[1], Length(PathEx), 1); {$ELSE} Hash:=jenkinsLookupHash2(@LowerCase(PathEx)[1], Length(PathEx), 1); {$ENDIF} HashIdx:=Hash mod fManifestHeader.HashTableKeyCount; HashFileIdx:=lpHashTableKeys[HashIdx]; if HashFileIdx=-1 then Exit; end; end; dec(HashFileIdx, fManifestHeader.HashTableKeyCount); repeat HashValue:=lpHashTableIndices[HashFileIdx]; FileID:=HashValue and $7FFFFFFF; end_block:= (HashValue and $80000000 = $80000000); if CompareStr(ItemPath[FileID], Path)=0 then begin result:=FileID; Exit; end; inc(HashFileIdx); until end_block; if (result=-1) and (LowerCase(Path)<>Path) then result:=GetItemByPath(LowerCase(Path)); end; 

コヌドからわかるように、ファむルぞのパス党䜓から名前のみを取埗し、そのハッシュを蚈算したす。 結果の敎数陀算の残りを倀ManifestHeader.HashTableKeyCountで取埗したす。これは、 0xffffffff そのような芁玠がない堎合たたは倀X + ManifestHeader.HashTableKeyCountを含むHashTableKeysリスト内の゚ントリの番号になりたす。 これに基づいお、 Xを蚈算したす。これは、 HashTableIndicesリスト内の芁玠の番号であり、 そこから怜玢察象の芁玠を芋぀けるこずができたす。 このリストの倀は、ク゚リで名前が比范される怜玢察象のアむテムを瀺したす。 䞀臎しない堎合、リストの次の芁玠を取埗し、芁玠番号の最䞊䜍ビットが「0」になるたで繰り返したす。
混乱するこずがわかったが、それがどのように機胜するかを理解しおいる。そのような混乱のためにVALVEプログラマヌを責めなさい。
この方法は、ツリヌでの盎接怜玢よりもはるかに優れおいたす。ゲヌムを開始するずきのパフォヌマンスを、Steam.dllの自己蚘述ラむブラリ゚ミュレヌタラむブラリず比范したした。これに぀いおは、匕き続き説明したす。

芁玠ぞのフルパスを取埗する

このアクションは以前のアクションにいくらか戻りたす-ツリヌを経由しおルヌト芁玠に移動し、ファむルぞのパスを取埗する必芁がある芁玠の数。

ファむルパスを取埗する
C ++
 char *CGCFFile::GetItemPath(UINT32 Item) { size_t len = strlen(&lpNames[lpManifest[Item].NameOffset]); UINT32 Idx = lpManifest[Item].ParentIndex; while (Idx != CACHE_INVALID_ITEM) { len += strlen(&lpNames[lpManifest[Idx].NameOffset]) + 1; Idx= lpManifest[Idx].ParentIndex; } len--; char *res = new char[len+1]; memset(res, 0, len+1); size_t l = strlen(&lpNames[lpManifest[Item].NameOffset]); memcpy(&res[len-l], &lpNames[lpManifest[Item].NameOffset], l); len -= strlen(&lpNames[lpManifest[Item].NameOffset]); res[--len] = '\\'; Item = lpManifest[Item].ParentIndex; while ((Item != CACHE_INVALID_ITEM) && (Item != 0)) { l = strlen(&lpNames[lpManifest[Item].NameOffset]); memcpy(&res[len-l], &lpNames[lpManifest[Item].NameOffset], l); len -= strlen(&lpNames[lpManifest[Item].NameOffset]); res[--len] = '\\'; Item = lpManifest[Item].ParentIndex; } return res; } 

デルファむ
 function TGCFFile.GetItemPath(Item: integer): string; var res: AnsiString; begin res:=pAnsiChar(@fNameTable[lpManifestNodes[Item].NameOffset+1]); Item:=lpManifestNodes[Item].ParentIndex; while (Item>-1) do begin res:=pAnsiChar(@fNameTable[lpManifestNodes[Item].NameOffset+1])+'\'+res; Item:=lpManifestNodes[Item].ParentIndex; end; Delete(res, 1, 1); {$IFDEF UNICODE} result:=Ansi2Wide(res); {$ELSE} result:=res; {$ENDIF} end; 

Delphiのコヌドは、C ++の堎合はstd :: stringクラスを䜿甚しなかったため、はるかに小さくなりたした-それに぀いおは知りたせんでした。 それにより、コヌドははるかに短くなりたす...

ストリヌム

アヌカむブのようなファむル圢匏他のファむルを含むのラむブラリを䜜成するずきは、「ストリヌムむンストリヌム」の原則を䜿甚したす。これにより、アヌカむブ内のファむルを展開せずに開くこずができたす。 たずえば、叀いバヌゞョンのキャッシュhalf-life.gcfには、アヌカむブであるファむルpak0.pakがありたした。 その結果、 half-life.gcfファむルをit- pak0.pakで開きたした 。 順番に、必芁なファむルを読みたす。 そしお、これらすべお-メモリにさえ解凍するこずなく、すべおの機胜は、ファむルストリヌム䜎レベル、WindowsAPIレベルで䜜成したラッパヌを介しお実装されたす。

キャッシュ内のファむルを開く
C ++
 CStream *CGCFFile::OpenFile(char* FileName, UINT8 Mode) { UINT32 Item = GetItem(FileName); if (Item == CACHE_INVALID_ITEM) return NULL; if ((lpManifest[Item].Attributes & CACHE_FLAG_FILE) != CACHE_FLAG_FILE) return NULL; return OpenFile(Item, Mode); } CStream *CGCFFile::OpenFile(UINT32 Item, UINT8 Mode) { StreamData *Data = new StreamData(); memset(Data, 0, sizeof(StreamData)); Data->Handle = (handle_t)Item; Data->Package = this; Data->Size = this->GetItemSize(Item).Size; if (IsNCF) Data->FileStream = (CStream*)new CStream(MakeStr(CommonPath, GetItemPath(Item)), Mode==CACHE_OPEN_WRITE); else BuildClustersTable(Item, &Data->Sectors); return new CStream(pStreamMethods, Data); } 

デルファむ
 function TGCFFile.OpenFile(FileName: string; Access: byte): TStream; var Item: integer; begin result:=nil; Item:=ItemByPath[FileName]; if (Item=-1) then Exit; if ((lpManifestNodes[Item].Attributes and HL_GCF_FLAG_FILE<>HL_GCF_FLAG_FILE) or (ItemSize[Item].Size=0)) then Exit; result:=OpenFile(Item, Access); end; function TGCFFile.OpenFile(Item: integer; Access: byte): TStream; var res: TStream; begin res:=TStream.CreateStreamOnStream(@StreamMethods); res.Data.fHandle:=ulong(Item); res.Data.Package:=self; res.Data.fSize:=(res.Data.Package as TGCFFile).ItemSize[Item].Size; res.Data.fPosition:=0; if (IsNCF) then begin CommonPath:=IncludeTrailingPathDelimiter(CommonPath); case Access of ACCES_READ: begin res.Data.FileStream:=TStream.CreateReadFileStream(CommonPath+ItemPath[Item]); res.Methods.fSetSiz:=StreamOnStream_SetSizeNULL; res.Methods.fWrite:=StreamOnStream_WriteNULL; end; ACCES_WRITE: begin ForceDirectories(ExtractFilePath(CommonPath+ItemPath[Item])); res.Data.FileStream:=TStream.CreateWriteFileStream(CommonPath+ItemPath[Item]); end; ACCES_READWRITE: res.Data.FileStream:=TStream.CreateReadWriteFileStream(CommonPath+ItemPath[Item]); end; res.Data.FileStream.Seek(0, spBegin); end else GCF_BuildClustersTable(Item, @res.Data.SectorsTable); result:=res; end; 

したがっお、コンテンツの操䜜が倧幅に簡玠化されたす-ファむルを開いお、䞍必芁なゞェスチャヌなしでファむルからデヌタを読み取るこずができたす。

チェックサムファむルの抜出

この手順では、䞊蚘のストリヌムが積極的に䜿甚されたす-固定サむズチェックサムの最倧フラグメントサむズは32Kbのフラグメントでファむルを読み取り、それらのチェックサムを蚈算し、ヘッダヌのテヌブルの倀ず比范したす。

COPを確認しおファむルを抜出する
C ++
 UINT64 CGCFFile::ExtractFile(UINT32 Item, char *Dest, bool IsValidation) { CStream *fileIn = this->OpenFile(Item, CACHE_OPEN_READ), *fileOut; if (fileIn == NULL) return 0; if (!IsValidation) { if (DirectoryExists(Dest)) Dest = MakeStr(IncludeTrailingPathDelimiter(Dest), GetItemName(Item)); fileOut = new CStream(Dest, true); if (fileOut->GetHandle() == INVALID_HANDLE_VALUE) return 0; fileOut->SetSize(GetItemSize(Item).Size); } UINT8 buf[CACHE_CHECKSUM_LENGTH]; UINT32 CheckSize = CACHE_CHECKSUM_LENGTH; UINT64 res = 0; while ((fileIn->Position()<fileIn->GetSize()) && (CheckSize == CACHE_CHECKSUM_LENGTH)) { if (Stop) break; UINT32 CheckIdx = lpFileIDChecksum[lpManifest[Item].FileId].FirstChecksumIndex + ((fileIn->Position() & 0xffffffffffff8000) >> 15); CheckSize = (UINT32)fileIn->Read(buf, CheckSize); UINT32 CheckFile = Checksum(buf, CheckSize), CheckFS = lpChecksum[CheckIdx]; if (CheckFile != CheckFS) { break; } else if (!IsValidation) { fileOut->Write(buf, CheckSize); } res += CheckSize; } delete fileIn; if (!IsValidation) delete fileOut; return res; } 

デルファむ
 function TGCFFile.ExtractFile(Item: integer; Dest: string; IsValidation: boolean = false): int64; var StreamF, StreamP: TStream; CheckSize, CheckFile, CheckFS, CheckIdx: uint32_t; buf: array of byte; Size: int64; begin result:=0; StreamP:=OpenFile(Item, ACCES_READ); if (StreamP=nil) then Exit; Size:=ItemSize[Item].Size; if Assigned(OnProgress) then OnProgress(ItemPath[Item], 0, Size, Data); if Assigned(OnProgressObj) then OnProgressObj(ItemPath[Item], 0, Size, Data); StreamF:=nil; if (not IsValidation) then begin if DirectoryExists(Dest) then Dest:=IncludeTrailingPathDelimiter(Dest)+ExtractFileName(ItemName[Item]); StreamF:=TStream.CreateWriteFileStream(Dest); StreamF.Size:=ItemSize[Item].Size; if StreamF.Handle=INVALID_HANDLE_VALUE then begin StreamF.Free; Exit; end; end; SetLength(buf, HL_GCF_CHECKSUM_LENGTH); CheckSize:=HL_GCF_CHECKSUM_LENGTH; while ((StreamP.Position<StreamP.Size) and (CheckSize=HL_GCF_CHECKSUM_LENGTH)) do begin CheckIdx:=lpFileIdChecksumTableEntries[lpManifestNodes[Item].FileId].FirstChecksumIndex+ ((StreamP.Position and $ffffffffffff8000) shr 15); CheckSize:=StreamP.Read(buf[0], HL_GCF_CHECKSUM_LENGTH); CheckFile:=Checksum(@buf[0], CheckSize); CheckFS:=lpChecksumEntries[CheckIdx]; if (CheckFile<>CheckFS) and (not IgnoreCheckError) then begin if Assigned(OnError) then OnError(GetItemPath(Item), ERROR_CHECKSUM, Data); if Assigned(OnErrorObj) then OnErrorObj(GetItemPath(Item), ERROR_CHECKSUM, Data); break; end else if (not IsValidation) then StreamF.Write(buf[0], CheckSize); inc(result, CheckSize); if Assigned(OnProgress) then OnProgress('', result, Size, Data); if Assigned(OnProgressObj) then OnProgressObj('', result, Size, Data); if Stop then break; end; SetLength(buf, 0); StreamP.Free; if (not IsValidation) then StreamF.Free; end; 

Delphiのコヌドには、䜜業の進行状況を衚瀺するための远加コヌドがありたす-OnProgress、OnProgressObjコヌルバック関数の呌び出し。

ファむルの内容を解読する

倚くのゲヌムはリリヌスの少し前に事前にダりンロヌドできるため、そのような堎合のコンテンツは完党たたは郚分的に暗号化されたす。 ゲヌムのリリヌスにより、このコンテンツを埩号化するためのキヌが利甚可胜になり、次のコヌドで実行されたす。

ファむル埩号化
C ++
 UCHAR IV[16] = {0}; void DecryptFileChunk(char *buf, UINT32 size, char *key) { AES_KEY aes_key; AES_set_decrypt_key((UCHAR*)key, 128, &aes_key); AES_cbc_encrypt((UCHAR*)buf, (UCHAR*)buf, size, &aes_key, IV, false); } UINT64 CGCFFile::DecryptFile(UINT32 Item, char *key) { UINT64 res = 0; CStream *str = OpenFile(Item, CACHE_OPEN_READWRITE); if (str == NULL) return 0; char buf[CACHE_CHECKSUM_LENGTH], dec[CACHE_CHECKSUM_LENGTH]; UINT32 CheckSize = CACHE_CHECKSUM_LENGTH; INT32 CompSize, UncompSize, sz; while ((str->Position() < str->GetSize()) && (CheckSize == CACHE_CHECKSUM_LENGTH)) { UINT32 CheckIdx = lpFileIDChecksum[lpManifest[Item].FileId].FirstChecksumIndex + ((str->Position() & 0xffffffffffff8000) >> 15); INT32 CheckSize = (INT32)str->Read(buf, 8); memcpy(&CompSize, &buf[0], 4); memcpy(&UncompSize, &buf[4], 4); if (((UINT32)UncompSize > pManifestHeader->CompressionBlockSize) || (CompSize > UncompSize) || (UncompSize < -1) || (CompSize < -1)) { // Chunk is not compressed CheckSize = (UINT32)str->Read(&buf[8], CACHE_CHECKSUM_LENGTH-8); DecryptFileChunk(&buf[0], CheckSize, key); } else if (((UINT32)UncompSize <= pManifestHeader->CompressionBlockSize) && (CompSize <= UncompSize) && (UncompSize > -1) || (CompSize > -1)) { // Chunk is compressed CheckSize = (UINT32)str->Read(&buf[8], UncompSize-8); INT32 CheckFile = UncompSize; if (CompSize%16 == 0) sz = CompSize; else sz = CompSize + 16 - (CompSize%16); memcpy(dec, buf, sz); DecryptFileChunk(&dec[0], sz, key); uncompress((Bytef*)&buf[0], (uLongf*)&CheckFile, (Bytef*)&dec[0], sz); } str->Seek(-CheckSize, USE_SEEK_CURRENT); str->Write(&buf[0], CheckSize); UINT32 Check1 = Checksum((UINT8*)&buf[0], CheckSize), Check2 = lpChecksum[CheckIdx]; if (Check1 != Check2) break; res += CheckSize; } lpManifest[Item].Attributes = lpManifest[Item].Attributes & (!CACHE_FLAG_ENCRYPTED); return res; } 

デルファむ
 const IV: array[0..15] of byte = (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); procedure DecryptFileChunk(buf: pByte; ChunkSize: integer; Key: Pointer); var AES: TCipher_Rijndael; src: array[0..HL_GCF_CHECKSUM_LENGTH-1] of byte; begin Move(buf^, src[0], HL_GCF_CHECKSUM_LENGTH); AES:=TCipher_Rijndael.Create(); AES.Init(Key^, 16, IV[0], 16); AES.Mode:=cmCFBx; AES.Decode(src[0], buf^, ChunkSize); AES.Free; end; function TGCFFile.DecryptFile(Item: integer; Key: Pointer): int64; var StreamP: TStream; CheckSize, CheckFile, CheckFS, CheckIdx, sz: uint32_t; buf: array of byte; dec: array[0..HL_GCF_CHECKSUM_LENGTH] of byte; CompSize, UncompSize: integer; Size: int64; begin result:=0; StreamP:=OpenFile(Item, ACCES_READWRITE); if (StreamP=nil) then Exit; Size:=ItemSize[Item].Size; if Assigned(OnProgress) then OnProgress(ItemName[Item], 0, Size, Data); if Assigned(OnProgressObj) then OnProgressObj(ItemName[Item], 0, Size, Data); SetLength(buf, HL_GCF_CHECKSUM_LENGTH); CheckSize:=HL_GCF_CHECKSUM_LENGTH; while ((StreamP.Position<StreamP.Size) and (CheckSize=HL_GCF_CHECKSUM_LENGTH)) do begin CheckIdx:=lpFileIdChecksumTableEntries[lpManifestNodes[Item].FileId].FirstChecksumIndex+ ((StreamP.Position and $ffffffffffff8000) shr 15); CheckSize:=StreamP.Read(buf[0], 8); Move(buf[0], CompSize, 4); Move(buf[4], UncompSize, 4); if (ulong(UncompSize)>fManifestHeader.CompressionBlockSize) or (CompSize>UncompSize) or (UncompSize<-1) or (CompSize<-1) then begin //Chunk is not compressed! CheckSize:=StreamP.Read(buf[8], HL_GCF_CHECKSUM_LENGTH-8); DecryptFileChunk(@buf[0], CheckSize, Key); end else if ((ulong(UncompSize)<=fManifestHeader.CompressionBlockSize) and (CompSize<=UncompSize)) and ((UncompSize>-1) and (CompSize>-1)) then begin CheckSize:=StreamP.Read(buf[8], UncompSize-8); CheckFile:=UncompSize; //Chunk is compressed! if (CompSize mod 16=0) then sz:=CompSize else sz:=CompSize+16-(CompSize mod 16); Move(buf[8], dec[0], sz); DecryptFileChunk(@dec[0], sz, Key); uncompress(@buf[0], CheckFile, @dec[0], sz); end; StreamP.Seek(-CheckSize, spCurrent); StreamP.Write(buf[0], CheckSize); CheckFile:=Checksum(@buf[0], CheckSize); CheckFS:=lpChecksumEntries[CheckIdx]; if (CheckFile<>CheckFS) and (not IgnoreCheckError) then begin if Assigned(OnError) then OnError(GetItemPath(Item), ERROR_CHECKSUM, Data); if Assigned(OnErrorObj) then OnErrorObj(GetItemPath(Item), ERROR_CHECKSUM, Data); break; end; inc(result, CheckSize); //StreamP.Position:=StreamP.Position+CheckSize; if Assigned(OnProgress) then OnProgress('', result, Size, Data); if Assigned(OnProgressObj) then OnProgressObj('', result, Size, Data); if Stop then break; end; lpManifestNodes[Item].Attributes:=lpManifestNodes[Item].Attributes and (not HL_GCF_FLAG_ENCRYPTED); fIsChangeHeader[HEADER_MANIFEST_NODES]:=true; SaveChanges(); SetLength(buf, 0); end; 


ManifestHeaderのチェックサム蚈算

この倀の蚈算には、次のヘッダヌ構造が䜿甚されたす。

CSを蚈算する前に、次のフィヌルドがリセットされたす。

蚈算自䜓は、ハッシュ関数の蚈算のシヌケンスに䜎枛さAdler32チェックすべおのこれらの構造のため

のDelphi
 function ManifestChecksum(Header: pCache_ManifestHeader; entries, names, hashs, table, MFP, UCF: pByte): uint32_t; var tmp1, tmp2: uint32; begin tmp1:=Header.Fingerprint; tmp2:=Header.Checksum; Header.Fingerprint:=0; Header.Checksum:=0; result:=adler32(0, pAnsiChar(Header), sizeof(TCache_ManifestHeader)); result:=adler32(result, pAnsiChar(entries), sizeof(TCache_ManifestNode)*Header^.NodeCount); result:=adler32(result, pAnsiChar(names), Header^.NameSize); result:=adler32(result, pAnsiChar(hashs), sizeof(uint32)*Header^.HashTableKeyCount); result:=adler32(result, pAnsiChar(table), sizeof(uint32)*Header^.NodeCount); if Header^.NumOfMinimumFootprintFiles>0 then result:=adler32(result, pAnsiChar(MFP), sizeof(uint32)*Header^.NumOfMinimumFootprintFiles); if Header^.NumOfUserConfigFiles>0 then result:=adler32(result, pAnsiChar(UCF), sizeof(uint32)*Header^.NumOfUserConfigFiles); Header.Fingerprint:=tmp1; Header.Checksum:=tmp2; end; 


おわりに


説明が煩雑であるためにこの蚘事では考慮されおいない他の機胜セクタヌマップの倉曎、このマップの再構築時など、ビゞヌセクタヌのビットマップの䜿甚などは、リポゞトリで芋るこずができたす以䞋で説明する残りのプログラムフラグメント蚘事。これらの゜ヌスコヌドは、プロゞェクトで䜿甚できたす誰かがそのような珍しいものを必芁ずする堎合。
すべおの゜ヌスコヌドの最終曎新のおおよその日付は、2011幎の埌半です。

PSこのラむブラリを曞くこずは、察象のオペレヌティングシステムに関するラボを曞くずきに非垞に圹立ちたした。倧孊では、ファむルシステムの動䜜ファむルの䜜成、曞き蟌み、読み取り、削陀をシミュレヌトする必芁がありたした。私の䜜品は最初であり、おそらく、唯䞀の䜜品であり、ブロックずセクタヌに分割されたファむルシステムのむメヌゞが䜿甚されたした。この䜜業の䞀環ずしお、キャッシュのデフラグツヌルも远加したした...

Source: https://habr.com/ru/post/J224027/


All Articles