1 /*
2 * HLLib
3 * Copyright (C) 2006-2010 Ryan Gregg
4
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later
9 * version.
10 */
11
12 #include "HLLib.h"
13 #include "GCFStream.h"
14
15 using namespace HLLib;
16 using namespace HLLib::Streams;
17
CGCFStream(const CGCFFile & GCFFile,hlUInt uiFileID)18 CGCFStream::CGCFStream(const CGCFFile &GCFFile, hlUInt uiFileID) : bOpened(hlFalse), uiMode(HL_MODE_INVALID), GCFFile(GCFFile), uiFileID(uiFileID), pView(0), uiPointer(0), uiLength(0)
19 {
20
21 }
22
~CGCFStream()23 CGCFStream::~CGCFStream()
24 {
25 this->Close();
26 }
27
GetType() const28 HLStreamType CGCFStream::GetType() const
29 {
30 return HL_STREAM_GCF;
31 }
32
GetPackage() const33 const CGCFFile &CGCFStream::GetPackage() const
34 {
35 return this->GCFFile;
36 }
37
GetFileName() const38 const hlChar *CGCFStream::GetFileName() const
39 {
40 return this->GCFFile.lpDirectoryNames + this->GCFFile.lpDirectoryEntries[this->uiFileID].uiNameOffset;
41 }
42
GetOpened() const43 hlBool CGCFStream::GetOpened() const
44 {
45 return this->bOpened;
46 }
47
GetMode() const48 hlUInt CGCFStream::GetMode() const
49 {
50 return this->uiMode;
51 }
52
Open(hlUInt uiMode)53 hlBool CGCFStream::Open(hlUInt uiMode)
54 {
55 this->Close();
56
57 if(!this->GCFFile.GetOpened())
58 {
59 LastError.SetErrorMessage("GCF file not opened.");
60 return hlFalse;
61 }
62
63 if((uiMode & (HL_MODE_READ | HL_MODE_WRITE)) == 0)
64 {
65 LastError.SetErrorMessageFormated("Invalid open mode (%#.8x).", uiMode);
66 return hlFalse;
67 }
68
69 if((uiMode & HL_MODE_READ) != 0 && (this->GCFFile.pMapping->GetMode() & HL_MODE_READ) == 0)
70 {
71 LastError.SetErrorMessage("GCF file does not have read permissions.");
72 return hlFalse;
73 }
74
75 if((uiMode & HL_MODE_WRITE) != 0 && (this->GCFFile.pMapping->GetMode() & HL_MODE_WRITE) == 0)
76 {
77 LastError.SetErrorMessage("GCF file does not have write permissions.");
78 return hlFalse;
79 }
80
81 this->uiPointer = 0;
82 this->uiLength = (uiMode & HL_MODE_READ) ? this->GCFFile.lpDirectoryEntries[this->uiFileID].uiItemSize : 0;
83
84 this->bOpened = hlTrue;
85 this->uiMode = uiMode;
86
87 this->uiBlockEntryIndex = this->GCFFile.lpDirectoryMapEntries[this->uiFileID].uiFirstBlockIndex;
88 this->uiBlockEntryOffset = 0;
89 this->uiDataBlockIndex = this->GCFFile.lpBlockEntries[this->uiBlockEntryIndex].uiFirstDataBlockIndex;
90 this->uiDataBlockOffset = 0;
91
92 return hlTrue;
93 }
94
Close()95 hlVoid CGCFStream::Close()
96 {
97 this->bOpened = hlFalse;
98 this->uiMode = HL_MODE_INVALID;
99
100 this->GCFFile.pMapping->Unmap(this->pView);
101
102 this->uiPointer = 0;
103 this->uiLength = 0;
104 }
105
GetStreamSize() const106 hlULongLong CGCFStream::GetStreamSize() const
107 {
108 return this->uiLength;
109 }
110
GetStreamPointer() const111 hlULongLong CGCFStream::GetStreamPointer() const
112 {
113 return this->uiPointer;
114 }
115
Seek(hlLongLong iOffset,HLSeekMode eSeekMode)116 hlULongLong CGCFStream::Seek(hlLongLong iOffset, HLSeekMode eSeekMode)
117 {
118 if(!this->bOpened)
119 {
120 return 0;
121 }
122
123 switch(eSeekMode)
124 {
125 case HL_SEEK_BEGINNING:
126 this->uiPointer = 0;
127 break;
128 case HL_SEEK_CURRENT:
129
130 break;
131 case HL_SEEK_END:
132 this->uiPointer = this->uiLength;
133 break;
134 }
135
136 hlLongLong iPointer = static_cast<hlLongLong>(this->uiPointer) + iOffset;
137
138 if(iPointer < 0)
139 {
140 iPointer = 0;
141 }
142 else if(iPointer > static_cast<hlLongLong>(this->uiLength))
143 {
144 iPointer = static_cast<hlLongLong>(this->uiLength);
145 }
146
147 this->uiPointer = static_cast<hlULongLong>(iPointer);
148
149 return this->uiPointer;
150 }
151
Read(hlChar & cChar)152 hlBool CGCFStream::Read(hlChar &cChar)
153 {
154 if(!this->bOpened)
155 {
156 return 0;
157 }
158
159 if((this->uiMode & HL_MODE_READ) == 0)
160 {
161 LastError.SetErrorMessage("Stream not in read mode.");
162 return 0;
163 }
164
165 if(this->uiPointer < this->uiLength)
166 {
167 if(!this->Map(this->uiPointer))
168 {
169 return 0;
170 }
171
172 hlULongLong uiViewPointer = this->uiPointer - (this->uiBlockEntryOffset + this->uiDataBlockOffset);
173 hlULongLong uiViewBytes = this->pView->GetLength() - uiViewPointer;
174
175 if(uiViewBytes >= 1)
176 {
177 cChar = *(static_cast<const hlChar *>(this->pView->GetView()) + uiViewPointer);
178 this->uiPointer++;
179 return 1;
180 }
181 }
182
183 return 0;
184 }
185
Read(hlVoid * lpData,hlUInt uiBytes)186 hlUInt CGCFStream::Read(hlVoid *lpData, hlUInt uiBytes)
187 {
188 if(!this->bOpened)
189 {
190 return 0;
191 }
192
193 if((this->uiMode & HL_MODE_READ) == 0)
194 {
195 LastError.SetErrorMessage("Stream not in read mode.");
196 return 0;
197 }
198
199 if(this->uiPointer == this->uiLength)
200 {
201 return 0;
202 }
203 else
204 {
205 hlULongLong uiOffset = 0;
206 while(uiBytes && this->uiPointer < this->uiLength)
207 {
208 if(!this->Map(this->uiPointer))
209 {
210 break;
211 }
212
213 hlULongLong uiViewPointer = this->uiPointer - (this->uiBlockEntryOffset + this->uiDataBlockOffset);
214 hlULongLong uiViewBytes = this->pView->GetLength() - uiViewPointer;
215
216 if(uiViewBytes >= static_cast<hlULongLong>(uiBytes))
217 {
218 memcpy(static_cast<hlByte *>(lpData) + uiOffset, static_cast<const hlByte *>(this->pView->GetView()) + uiViewPointer, uiBytes);
219 this->uiPointer += static_cast<hlULongLong>(uiBytes);
220 uiOffset += uiBytes;
221 break;
222 }
223 else
224 {
225 memcpy(static_cast<hlByte *>(lpData) + uiOffset, static_cast<const hlByte *>(this->pView->GetView()) + uiViewPointer, static_cast<size_t>(uiViewBytes));
226 this->uiPointer += uiViewBytes;
227 uiOffset += uiViewBytes;
228 uiBytes -= static_cast<hlUInt>(uiViewBytes);
229 }
230 }
231
232 return static_cast<hlUInt>(uiOffset);
233 }
234 }
235
Write(hlChar cChar)236 hlBool CGCFStream::Write(hlChar cChar)
237 {
238 if(!this->bOpened)
239 {
240 return 0;
241 }
242
243 if((this->uiMode & HL_MODE_WRITE) == 0)
244 {
245 LastError.SetErrorMessage("Stream not in write mode.");
246 return 0;
247 }
248
249 if(this->uiPointer < this->GCFFile.lpDirectoryEntries[this->uiFileID].uiItemSize)
250 {
251 if(!this->Map(this->uiPointer))
252 {
253 return 0;
254 }
255
256 hlULongLong uiViewPointer = this->uiPointer - (this->uiBlockEntryOffset + this->uiDataBlockOffset);
257 hlULongLong uiViewBytes = this->pView->GetLength() - uiViewPointer;
258
259 if(uiViewBytes >= 1)
260 {
261 *(static_cast<hlChar *>(const_cast<hlVoid *>(this->pView->GetView())) + uiViewPointer) = cChar;
262 this->uiPointer++;
263
264 if(this->uiPointer > this->uiLength)
265 {
266 this->uiLength = this->uiPointer;
267 }
268
269 return 1;
270 }
271 }
272
273 return 0;
274 }
275
Write(const hlVoid * lpData,hlUInt uiBytes)276 hlUInt CGCFStream::Write(const hlVoid *lpData, hlUInt uiBytes)
277 {
278 if(!this->bOpened)
279 {
280 return 0;
281 }
282
283 if((this->uiMode & HL_MODE_WRITE) == 0)
284 {
285 LastError.SetErrorMessage("Stream not in write mode.");
286 return 0;
287 }
288
289 if(this->uiPointer == this->GCFFile.lpDirectoryEntries[this->uiFileID].uiItemSize)
290 {
291 return 0;
292 }
293 else
294 {
295 hlULongLong uiOffset = 0;
296 while(uiBytes && this->uiPointer < this->GCFFile.lpDirectoryEntries[this->uiFileID].uiItemSize)
297 {
298 if(!this->Map(this->uiPointer))
299 {
300 break;
301 }
302
303 hlULongLong uiViewPointer = this->uiPointer - (this->uiBlockEntryOffset + this->uiDataBlockOffset);
304 hlULongLong uiViewBytes = this->pView->GetLength() - uiViewPointer;
305
306 if(uiViewBytes >= uiBytes)
307 {
308 memcpy(static_cast<hlByte *>(const_cast<hlVoid *>(this->pView->GetView())) + uiViewPointer, static_cast<const hlByte *>(lpData) + uiOffset, uiBytes);
309 this->uiPointer += static_cast<hlULongLong>(uiBytes);
310 uiOffset += uiBytes;
311 break;
312 }
313 else
314 {
315 memcpy(static_cast<hlByte *>(const_cast<hlVoid *>(this->pView->GetView())) + uiViewPointer, static_cast<const hlByte *>(lpData) + uiOffset, static_cast<size_t>(uiViewBytes));
316 this->uiPointer += uiViewBytes;
317 uiOffset += uiViewBytes;
318 uiBytes -= static_cast<hlUInt>(uiViewBytes);
319 }
320 }
321
322 if(this->uiPointer > this->uiLength)
323 {
324 this->uiLength = this->uiPointer;
325 }
326
327 return static_cast<hlUInt>(uiOffset);
328 }
329 }
330
Map(hlULongLong uiPointer)331 hlBool CGCFStream::Map(hlULongLong uiPointer)
332 {
333 if(uiPointer < this->uiBlockEntryOffset + this->uiDataBlockOffset)
334 {
335 this->uiBlockEntryIndex = this->GCFFile.lpDirectoryMapEntries[this->uiFileID].uiFirstBlockIndex;
336 this->uiBlockEntryOffset = 0;
337 this->uiDataBlockIndex = this->GCFFile.lpBlockEntries[this->uiBlockEntryIndex].uiFirstDataBlockIndex;
338 this->uiDataBlockOffset = 0;
339 }
340
341 hlULongLong uiLength = this->uiDataBlockOffset + this->GCFFile.pDataBlockHeader->uiBlockSize > this->GCFFile.lpBlockEntries[this->uiBlockEntryIndex].uiFileDataSize ? this->GCFFile.lpBlockEntries[this->uiBlockEntryIndex].uiFileDataSize - this->uiDataBlockOffset : this->GCFFile.pDataBlockHeader->uiBlockSize;
342 //hlUInt uiDataBlockTerminator = this->pDataBlockHeader->uiBlockCount >= 0x0000ffff ? 0xffffffff : 0x0000ffff;
343 hlUInt uiDataBlockTerminator = this->GCFFile.pFragmentationMapHeader->uiTerminator == 0 ? 0x0000ffff : 0xffffffff;
344
345 while((uiPointer >= this->uiBlockEntryOffset + this->uiDataBlockOffset + uiLength) && (this->uiBlockEntryIndex != this->GCFFile.pDataBlockHeader->uiBlockCount))
346 {
347 // Loop through each data block fragment.
348 while((uiPointer >= this->uiBlockEntryOffset + this->uiDataBlockOffset + uiLength) && (this->uiDataBlockIndex < uiDataBlockTerminator && this->uiDataBlockOffset < this->GCFFile.lpBlockEntries[this->uiBlockEntryIndex].uiFileDataSize))
349 {
350 // Get the next data block fragment.
351 this->uiDataBlockIndex = this->GCFFile.lpFragmentationMap[this->uiDataBlockIndex].uiNextDataBlockIndex;
352 this->uiDataBlockOffset += static_cast<hlULongLong>(this->GCFFile.pDataBlockHeader->uiBlockSize);
353
354 uiLength = this->uiDataBlockOffset + this->GCFFile.pDataBlockHeader->uiBlockSize > this->GCFFile.lpBlockEntries[this->uiBlockEntryIndex].uiFileDataSize ? static_cast<hlULongLong>(this->GCFFile.lpBlockEntries[this->uiBlockEntryIndex].uiFileDataSize) - this->uiDataBlockOffset : static_cast<hlULongLong>(this->GCFFile.pDataBlockHeader->uiBlockSize);
355 }
356
357 if(this->uiDataBlockOffset >= static_cast<hlULongLong>(this->GCFFile.lpBlockEntries[this->uiBlockEntryIndex].uiFileDataSize))
358 {
359 // Get the next data block.
360 this->uiBlockEntryOffset += static_cast<hlULongLong>(this->GCFFile.lpBlockEntries[this->uiBlockEntryIndex].uiFileDataSize);
361 this->uiBlockEntryIndex = this->GCFFile.lpBlockEntries[this->uiBlockEntryIndex].uiNextBlockEntryIndex;
362
363 this->uiDataBlockOffset = 0;
364 if(this->uiBlockEntryIndex != this->GCFFile.pDataBlockHeader->uiBlockCount)
365 {
366 this->uiDataBlockIndex = this->GCFFile.lpBlockEntries[this->uiBlockEntryIndex].uiFirstDataBlockIndex;
367 }
368
369 uiLength = this->uiDataBlockOffset + this->GCFFile.pDataBlockHeader->uiBlockSize > this->GCFFile.lpBlockEntries[this->uiBlockEntryIndex].uiFileDataSize ? static_cast<hlULongLong>(this->GCFFile.lpBlockEntries[this->uiBlockEntryIndex].uiFileDataSize) - this->uiDataBlockOffset : static_cast<hlULongLong>(this->GCFFile.pDataBlockHeader->uiBlockSize);
370 }
371 }
372
373 if(this->uiBlockEntryIndex == this->GCFFile.pDataBlockHeader->uiBlockCount || this->uiDataBlockIndex >= uiDataBlockTerminator)
374 {
375 if(this->uiBlockEntryOffset + this->uiDataBlockOffset < static_cast<hlULongLong>(this->GCFFile.lpDirectoryEntries[this->uiFileID].uiItemSize))
376 {
377 #ifdef _WIN32
378 LastError.SetErrorMessageFormated("Unexpected end of GCF stream (%I64u B of %u B). Has the GCF file been completely acquired?", this->uiBlockEntryOffset + this->uiDataBlockOffset, this->GCFFile.lpDirectoryEntries[this->uiFileID].uiItemSize);
379 #else
380 LastError.SetErrorMessageFormated("Unexpected end of GCF stream (%llu B of %u B). Has the GCF file been completely acquired?", this->uiBlockEntryOffset + this->uiDataBlockOffset, this->GCFFile.lpDirectoryEntries[this->uiFileID].uiItemSize);
381 #endif
382 }
383
384 this->GCFFile.pMapping->Unmap(this->pView);
385 return hlFalse;
386 }
387
388 if(this->pView)
389 {
390 if(this->pView->GetAllocationOffset() == this->GCFFile.pDataBlockHeader->uiFirstBlockOffset + this->uiDataBlockIndex * this->GCFFile.pDataBlockHeader->uiBlockSize)
391 {
392 return hlTrue;
393 }
394 }
395
396 return this->GCFFile.pMapping->Map(this->pView, static_cast<hlULongLong>(this->GCFFile.pDataBlockHeader->uiFirstBlockOffset) + static_cast<hlULongLong>(this->uiDataBlockIndex) * static_cast<hlULongLong>(this->GCFFile.pDataBlockHeader->uiBlockSize), uiLength);
397 }
398