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