1 /*****************************************************************************/
2 /* FileStream.cpp Copyright (c) Ladislav Zezula 2010 */
3 /*---------------------------------------------------------------------------*/
4 /* File stream support for StormLib */
5 /* */
6 /* Windows support: Written by Ladislav Zezula */
7 /* Mac support: Written by Sam Wilkins */
8 /* Linux support: Written by Sam Wilkins and Ivan Komissarov */
9 /* Big-endian: Written & debugged by Sam Wilkins */
10 /*---------------------------------------------------------------------------*/
11 /* Date Ver Who Comment */
12 /* -------- ---- --- ------- */
13 /* 11.06.10 1.00 Lad Derived from StormPortMac.cpp and StormPortLinux.cpp */
14 /*****************************************************************************/
15
16 #define __STORMLIB_SELF__
17 #include "StormLib.h"
18 #include "StormCommon.h"
19 #include "FileStream.h"
20
21 #ifdef __vita__
22 #include <psp2/rtc.h>
23 #define mmap(ptr, size, c, d, e, f) malloc(size)
24 #define munmap(ptr, size) free(ptr)
25 #define PROT_READ 0
26 #define MAP_PRIVATE 0
27 #endif
28
29
30
31 #ifdef _MSC_VER
32 #pragma comment(lib, "wininet.lib") // Internet functions for HTTP stream
33 #pragma warning(disable: 4800) // 'BOOL' : forcing value to bool 'true' or 'false' (performance warning)
34 #endif
35
36 //-----------------------------------------------------------------------------
37 // Local defines
38
39 #ifndef INVALID_HANDLE_VALUE
40 #define INVALID_HANDLE_VALUE ((HANDLE)-1)
41 #endif
42
43 //-----------------------------------------------------------------------------
44 // Local functions - platform-specific functions
45
46 #ifdef FULL
47 #ifndef PLATFORM_WINDOWS
48 static DWORD nLastError = ERROR_SUCCESS;
49
GetLastError()50 DWORD GetLastError()
51 {
52 return nLastError;
53 }
54
SetLastError(DWORD nError)55 void SetLastError(DWORD nError)
56 {
57 nLastError = nError;
58 }
59 #endif
60 #endif
61
StringToInt(const char * szString)62 static DWORD StringToInt(const char * szString)
63 {
64 DWORD dwValue = 0;
65
66 while('0' <= szString[0] && szString[0] <= '9')
67 {
68 dwValue = (dwValue * 10) + (szString[0] - '9');
69 szString++;
70 }
71
72 return dwValue;
73 }
74
75 //-----------------------------------------------------------------------------
76 // Dummy init function
77
BaseNone_Init(TFileStream *)78 static void BaseNone_Init(TFileStream *)
79 {
80 // Nothing here
81 }
82
83 //-----------------------------------------------------------------------------
84 // Local functions - base file support
85
BaseFile_Create(TFileStream * pStream)86 static bool BaseFile_Create(TFileStream * pStream)
87 {
88 #ifdef PLATFORM_WINDOWS
89 {
90 DWORD dwWriteShare = (pStream->dwFlags & STREAM_FLAG_WRITE_SHARE) ? FILE_SHARE_WRITE : 0;
91
92 pStream->Base.File.hFile = CreateFile(pStream->szFileName,
93 GENERIC_READ | GENERIC_WRITE,
94 dwWriteShare | FILE_SHARE_READ,
95 NULL,
96 CREATE_ALWAYS,
97 0,
98 NULL);
99 if(pStream->Base.File.hFile == INVALID_HANDLE_VALUE)
100 return false;
101 }
102 #endif
103
104 #if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) || defined(PLATFORM_SWITCH) || defined(PLATFORM_CTR) || defined(PLATFORM_VITA)
105 {
106 intptr_t handle;
107
108 handle = open(pStream->szFileName, O_RDWR | O_CREAT | O_TRUNC | O_LARGEFILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
109 if(handle == -1)
110 {
111 nLastError = errno;
112 pStream->Base.File.hFile = INVALID_HANDLE_VALUE; // BUGFIX (devilutionX)
113 return false;
114 }
115
116 pStream->Base.File.hFile = (HANDLE)handle;
117 }
118 #endif
119
120 // Reset the file size and position
121 pStream->Base.File.FileSize = 0;
122 pStream->Base.File.FilePos = 0;
123 return true;
124 }
125
BaseFile_Open(TFileStream * pStream,const TCHAR * szFileName,DWORD dwStreamFlags)126 static bool BaseFile_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD dwStreamFlags)
127 {
128 #ifdef PLATFORM_WINDOWS
129 {
130 ULARGE_INTEGER FileSize;
131 DWORD dwWriteAccess = (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? 0 : FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES;
132 DWORD dwWriteShare = (dwStreamFlags & STREAM_FLAG_WRITE_SHARE) ? FILE_SHARE_WRITE : 0;
133
134 // Open the file
135 pStream->Base.File.hFile = CreateFile(szFileName,
136 FILE_READ_DATA | FILE_READ_ATTRIBUTES | dwWriteAccess,
137 FILE_SHARE_READ | dwWriteShare,
138 NULL,
139 OPEN_EXISTING,
140 0,
141 NULL);
142 if(pStream->Base.File.hFile == INVALID_HANDLE_VALUE)
143 return false;
144
145 // Query the file size
146 FileSize.LowPart = GetFileSize(pStream->Base.File.hFile, &FileSize.HighPart);
147 pStream->Base.File.FileSize = FileSize.QuadPart;
148
149 // Query last write time
150 GetFileTime(pStream->Base.File.hFile, NULL, NULL, (LPFILETIME)&pStream->Base.File.FileTime);
151 }
152 #endif
153
154 #if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) || defined(PLATFORM_SWITCH) || defined(PLATFORM_CTR) || defined(PLATFORM_VITA)
155 {
156 struct stat64 fileinfo;
157 int oflag = (dwStreamFlags & STREAM_FLAG_READ_ONLY) ? O_RDONLY : O_RDWR;
158 intptr_t handle;
159
160 // Open the file
161 handle = open(szFileName, oflag | O_LARGEFILE);
162 if(handle == -1)
163 {
164 nLastError = errno;
165 pStream->Base.File.hFile = INVALID_HANDLE_VALUE; // BUGFIX (devilutionX)
166 return false;
167 }
168
169 // Get the file size
170 if(fstat64(handle, &fileinfo) == -1)
171 {
172 nLastError = errno;
173 close(handle);
174 pStream->Base.File.hFile = INVALID_HANDLE_VALUE; // BUGFIX (devilutionX)
175 return false;
176 }
177
178 // time_t is number of seconds since 1.1.1970, UTC.
179 // 1 second = 10000000 (decimal) in FILETIME
180 // Set the start to 1.1.1970 00:00:00
181 pStream->Base.File.FileTime = 0x019DB1DED53E8000ULL + (10000000 * fileinfo.st_mtime);
182 pStream->Base.File.FileSize = (ULONGLONG)fileinfo.st_size;
183 pStream->Base.File.hFile = (HANDLE)handle;
184 }
185 #endif
186
187 // Reset the file position
188 pStream->Base.File.FilePos = 0;
189 return true;
190 }
191
BaseFile_Read(TFileStream * pStream,ULONGLONG * pByteOffset,void * pvBuffer,DWORD dwBytesToRead)192 static bool BaseFile_Read(
193 TFileStream * pStream, // Pointer to an open stream
194 ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position
195 void * pvBuffer, // Pointer to data to be read
196 DWORD dwBytesToRead) // Number of bytes to read from the file
197 {
198 ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.File.FilePos;
199 DWORD dwBytesRead = 0; // Must be set by platform-specific code
200
201 #ifdef PLATFORM_WINDOWS
202 {
203 // Note: StormLib no longer supports Windows 9x.
204 // Thus, we can use the OVERLAPPED structure to specify
205 // file offset to read from file. This allows us to skip
206 // one system call to SetFilePointer
207
208 // Update the byte offset
209 pStream->Base.File.FilePos = ByteOffset;
210
211 // Read the data
212 if(dwBytesToRead != 0)
213 {
214 OVERLAPPED Overlapped;
215
216 Overlapped.OffsetHigh = (DWORD)(ByteOffset >> 32);
217 Overlapped.Offset = (DWORD)ByteOffset;
218 Overlapped.hEvent = NULL;
219 if(!ReadFile(pStream->Base.File.hFile, pvBuffer, dwBytesToRead, &dwBytesRead, &Overlapped))
220 return false;
221 }
222 }
223 #endif
224
225 #if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) || defined(PLATFORM_SWITCH) || defined(PLATFORM_CTR) || defined(PLATFORM_VITA)
226 {
227 ssize_t bytes_read;
228
229 // If the byte offset is different from the current file position,
230 // we have to update the file position xxx
231 if(ByteOffset != pStream->Base.File.FilePos)
232 {
233 lseek64((intptr_t)pStream->Base.File.hFile, (off64_t)(ByteOffset), SEEK_SET);
234 pStream->Base.File.FilePos = ByteOffset;
235 }
236
237 // Perform the read operation
238 if(dwBytesToRead != 0)
239 {
240 bytes_read = read((intptr_t)pStream->Base.File.hFile, pvBuffer, (size_t)dwBytesToRead);
241 if(bytes_read == -1)
242 {
243 nLastError = errno;
244 return false;
245 }
246
247 dwBytesRead = (DWORD)(size_t)bytes_read;
248 }
249 }
250 #endif
251
252 // Increment the current file position by number of bytes read
253 // If the number of bytes read doesn't match to required amount, return false
254 pStream->Base.File.FilePos = ByteOffset + dwBytesRead;
255 if(dwBytesRead != dwBytesToRead)
256 SetLastError(ERROR_HANDLE_EOF);
257 return (dwBytesRead == dwBytesToRead);
258 }
259
260 /**
261 * \a pStream Pointer to an open stream
262 * \a pByteOffset Pointer to file byte offset. If NULL, writes to current position
263 * \a pvBuffer Pointer to data to be written
264 * \a dwBytesToWrite Number of bytes to write to the file
265 */
266
BaseFile_Write(TFileStream * pStream,ULONGLONG * pByteOffset,const void * pvBuffer,DWORD dwBytesToWrite)267 static bool BaseFile_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite)
268 {
269 ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.File.FilePos;
270 DWORD dwBytesWritten = 0; // Must be set by platform-specific code
271
272 #ifdef PLATFORM_WINDOWS
273 {
274 // Note: StormLib no longer supports Windows 9x.
275 // Thus, we can use the OVERLAPPED structure to specify
276 // file offset to read from file. This allows us to skip
277 // one system call to SetFilePointer
278
279 // Update the byte offset
280 pStream->Base.File.FilePos = ByteOffset;
281
282 // Read the data
283 if(dwBytesToWrite != 0)
284 {
285 OVERLAPPED Overlapped;
286
287 Overlapped.OffsetHigh = (DWORD)(ByteOffset >> 32);
288 Overlapped.Offset = (DWORD)ByteOffset;
289 Overlapped.hEvent = NULL;
290 if(!WriteFile(pStream->Base.File.hFile, pvBuffer, dwBytesToWrite, &dwBytesWritten, &Overlapped))
291 return false;
292 }
293 }
294 #endif
295
296 #if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) || defined(PLATFORM_SWITCH) || defined(PLATFORM_CTR) || defined(PLATFORM_VITA)
297 {
298 ssize_t bytes_written;
299
300 // If the byte offset is different from the current file position,
301 // we have to update the file position
302 if(ByteOffset != pStream->Base.File.FilePos)
303 {
304 lseek64((intptr_t)pStream->Base.File.hFile, (off64_t)(ByteOffset), SEEK_SET);
305 pStream->Base.File.FilePos = ByteOffset;
306 }
307
308 // Perform the read operation
309 bytes_written = write((intptr_t)pStream->Base.File.hFile, pvBuffer, (size_t)dwBytesToWrite);
310 if(bytes_written == -1)
311 {
312 nLastError = errno;
313 return false;
314 }
315
316 dwBytesWritten = (DWORD)(size_t)bytes_written;
317 }
318 #endif
319
320 // Increment the current file position by number of bytes read
321 pStream->Base.File.FilePos = ByteOffset + dwBytesWritten;
322
323 // Also modify the file size, if needed
324 if(pStream->Base.File.FilePos > pStream->Base.File.FileSize)
325 pStream->Base.File.FileSize = pStream->Base.File.FilePos;
326
327 if(dwBytesWritten != dwBytesToWrite)
328 SetLastError(ERROR_DISK_FULL);
329 return (dwBytesWritten == dwBytesToWrite);
330 }
331
332 /**
333 * \a pStream Pointer to an open stream
334 * \a NewFileSize New size of the file
335 */
BaseFile_Resize(TFileStream * pStream,ULONGLONG NewFileSize)336 static bool BaseFile_Resize(TFileStream * pStream, ULONGLONG NewFileSize)
337 {
338 #ifdef PLATFORM_WINDOWS
339 {
340 LONG FileSizeHi = (LONG)(NewFileSize >> 32);
341 LONG FileSizeLo;
342 DWORD dwNewPos;
343 bool bResult;
344
345 // Set the position at the new file size
346 dwNewPos = SetFilePointer(pStream->Base.File.hFile, (LONG)NewFileSize, &FileSizeHi, FILE_BEGIN);
347 if(dwNewPos == INVALID_SET_FILE_POINTER && GetLastError() != ERROR_SUCCESS)
348 return false;
349
350 // Set the current file pointer as the end of the file
351 bResult = (bool)SetEndOfFile(pStream->Base.File.hFile);
352 if(bResult)
353 pStream->Base.File.FileSize = NewFileSize;
354
355 // Restore the file position
356 FileSizeHi = (LONG)(pStream->Base.File.FilePos >> 32);
357 FileSizeLo = (LONG)(pStream->Base.File.FilePos);
358 SetFilePointer(pStream->Base.File.hFile, FileSizeLo, &FileSizeHi, FILE_BEGIN);
359 return bResult;
360 }
361 #endif
362
363 #if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) || defined(PLATFORM_SWITCH) || defined(PLATFORM_CTR) || defined(PLATFORM_VITA)
364 {
365 if(ftruncate64((intptr_t)pStream->Base.File.hFile, (off64_t)NewFileSize) == -1)
366 {
367 nLastError = errno;
368 return false;
369 }
370
371 pStream->Base.File.FileSize = NewFileSize;
372 return true;
373 }
374 #endif
375 }
376
377 // Gives the current file size
BaseFile_GetSize(TFileStream * pStream,ULONGLONG * pFileSize)378 static bool BaseFile_GetSize(TFileStream * pStream, ULONGLONG * pFileSize)
379 {
380 // Note: Used by all thre base providers.
381 // Requires the TBaseData union to have the same layout for all three base providers
382 *pFileSize = pStream->Base.File.FileSize;
383 return true;
384 }
385
386 // Gives the current file position
BaseFile_GetPos(TFileStream * pStream,ULONGLONG * pByteOffset)387 static bool BaseFile_GetPos(TFileStream * pStream, ULONGLONG * pByteOffset)
388 {
389 // Note: Used by all thre base providers.
390 // Requires the TBaseData union to have the same layout for all three base providers
391 *pByteOffset = pStream->Base.File.FilePos;
392 return true;
393 }
394
395 // Renames the file pointed by pStream so that it contains data from pNewStream
BaseFile_Replace(TFileStream * pStream,TFileStream * pNewStream)396 static bool BaseFile_Replace(TFileStream * pStream, TFileStream * pNewStream)
397 {
398 #ifdef PLATFORM_WINDOWS
399 // Delete the original stream file. Don't check the result value,
400 // because if the file doesn't exist, it would fail
401 DeleteFile(pStream->szFileName);
402
403 // Rename the new file to the old stream's file
404 return (bool)MoveFile(pNewStream->szFileName, pStream->szFileName);
405 #endif
406
407 #if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) || defined(PLATFORM_SWITCH) || defined(PLATFORM_CTR) || defined(PLATFORM_VITA)
408 // "rename" on Linux also works if the target file exists
409 if(rename(pNewStream->szFileName, pStream->szFileName) == -1)
410 {
411 nLastError = errno;
412 return false;
413 }
414
415 return true;
416 #endif
417 }
418
BaseFile_Close(TFileStream * pStream)419 static void BaseFile_Close(TFileStream * pStream)
420 {
421 if(pStream->Base.File.hFile != INVALID_HANDLE_VALUE)
422 {
423 #ifdef PLATFORM_WINDOWS
424 CloseHandle(pStream->Base.File.hFile);
425 #endif
426
427 #if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) || defined(PLATFORM_SWITCH) || defined(PLATFORM_CTR) || defined(PLATFORM_VITA)
428 close((intptr_t)pStream->Base.File.hFile);
429 #endif
430 }
431
432 // Also invalidate the handle
433 pStream->Base.File.hFile = INVALID_HANDLE_VALUE;
434 }
435
436 // Initializes base functions for the disk file
BaseFile_Init(TFileStream * pStream)437 static void BaseFile_Init(TFileStream * pStream)
438 {
439 pStream->BaseCreate = BaseFile_Create;
440 pStream->BaseOpen = BaseFile_Open;
441 pStream->BaseRead = BaseFile_Read;
442 pStream->BaseWrite = BaseFile_Write;
443 pStream->BaseResize = BaseFile_Resize;
444 pStream->BaseGetSize = BaseFile_GetSize;
445 pStream->BaseGetPos = BaseFile_GetPos;
446 pStream->BaseClose = BaseFile_Close;
447 }
448
449 //-----------------------------------------------------------------------------
450 // Local functions - base memory-mapped file support
451
BaseMap_Open(TFileStream * pStream,const TCHAR * szFileName,DWORD dwStreamFlags)452 static bool BaseMap_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD dwStreamFlags)
453 {
454 #ifdef PLATFORM_WINDOWS
455
456 ULARGE_INTEGER FileSize;
457 HANDLE hFile;
458 HANDLE hMap;
459 bool bResult = false;
460
461 // Keep compiler happy
462 dwStreamFlags = dwStreamFlags;
463
464 // Open the file for read access
465 hFile = CreateFile(szFileName, FILE_READ_DATA, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
466 if(hFile != INVALID_HANDLE_VALUE)
467 {
468 // Retrieve file size. Don't allow mapping file of a zero size.
469 FileSize.LowPart = GetFileSize(hFile, &FileSize.HighPart);
470 if(FileSize.QuadPart != 0)
471 {
472 // Now create mapping object
473 hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
474 if(hMap != NULL)
475 {
476 // Map the entire view into memory
477 // Note that this operation will fail if the file can't fit
478 // into usermode address space
479 pStream->Base.Map.pbFile = (LPBYTE)MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
480 if(pStream->Base.Map.pbFile != NULL)
481 {
482 // Retrieve file time
483 GetFileTime(hFile, NULL, NULL, (LPFILETIME)&pStream->Base.Map.FileTime);
484
485 // Retrieve file size and position
486 pStream->Base.Map.FileSize = FileSize.QuadPart;
487 pStream->Base.Map.FilePos = 0;
488 bResult = true;
489 }
490
491 // Close the map handle
492 CloseHandle(hMap);
493 }
494 }
495
496 // Close the file handle
497 CloseHandle(hFile);
498 }
499
500 // If the file is not there and is not available for random access,
501 // report error
502 if(bResult == false)
503 return false;
504 #endif
505
506 #if defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU) || defined(PLATFORM_AMIGA) || defined(PLATFORM_SWITCH) || defined(PLATFORM_CTR) || defined(PLATFORM_VITA)
507 struct stat64 fileinfo;
508 intptr_t handle;
509 bool bResult = false;
510
511 // Open the file
512 handle = open(szFileName, O_RDONLY);
513 if(handle != -1)
514 {
515 // Get the file size
516 if(fstat64(handle, &fileinfo) != -1)
517 {
518 #if !defined(PLATFORM_AMIGA)
519 #if defined(PLATFORM_SWITCH) || defined(PLATFORM_CTR) || defined(PLATFORM_VITA)
520 pStream->Base.Map.pbFile = (LPBYTE)malloc((size_t)fileinfo.st_size);
521 #else
522 pStream->Base.Map.pbFile = (LPBYTE)mmap(NULL, (size_t)fileinfo.st_size, PROT_READ, MAP_PRIVATE, handle, 0);
523 #endif
524 if(pStream->Base.Map.pbFile != NULL)
525 {
526 // time_t is number of seconds since 1.1.1970, UTC.
527 // 1 second = 10000000 (decimal) in FILETIME
528 // Set the start to 1.1.1970 00:00:00
529 pStream->Base.Map.FileTime = 0x019DB1DED53E8000ULL + (10000000 * fileinfo.st_mtime);
530 pStream->Base.Map.FileSize = (ULONGLONG)fileinfo.st_size;
531 pStream->Base.Map.FilePos = 0;
532 bResult = true;
533 }
534 #endif
535 }
536 close(handle);
537 }
538
539 // Did the mapping fail?
540 if(bResult == false)
541 {
542 nLastError = errno;
543 return false;
544 }
545 #endif
546
547 return true;
548 }
549
BaseMap_Read(TFileStream * pStream,ULONGLONG * pByteOffset,void * pvBuffer,DWORD dwBytesToRead)550 static bool BaseMap_Read(
551 TFileStream * pStream, // Pointer to an open stream
552 ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position
553 void * pvBuffer, // Pointer to data to be read
554 DWORD dwBytesToRead) // Number of bytes to read from the file
555 {
556 ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.Map.FilePos;
557
558 // Do we have to read anything at all?
559 if(dwBytesToRead != 0)
560 {
561 // Don't allow reading past file size
562 if((ByteOffset + dwBytesToRead) > pStream->Base.Map.FileSize)
563 return false;
564
565 // Copy the required data
566 memcpy(pvBuffer, pStream->Base.Map.pbFile + (size_t)ByteOffset, dwBytesToRead);
567 }
568
569 // Move the current file position
570 pStream->Base.Map.FilePos += dwBytesToRead;
571 return true;
572 }
573
BaseMap_Close(TFileStream * pStream)574 static void BaseMap_Close(TFileStream * pStream)
575 {
576 #ifdef PLATFORM_WINDOWS
577 if(pStream->Base.Map.pbFile != NULL)
578 UnmapViewOfFile(pStream->Base.Map.pbFile);
579 #endif
580
581 #if (defined(PLATFORM_MAC) || defined(PLATFORM_LINUX) || defined(PLATFORM_HAIKU)) && !defined(PLATFORM_AMIGA) && !defined(PLATFORM_SWITCH) && !defined(PLATFORM_CTR) && !defined(PLATFORM_VITA)
582 //Todo(Amiga): Fix a proper solution for this
583 if(pStream->Base.Map.pbFile != NULL)
584 munmap(pStream->Base.Map.pbFile, (size_t )pStream->Base.Map.FileSize);
585 #elif defined(PLATFORM_SWITCH) || defined(PLATFORM_CTR) || defined(PLATFORM_VITA)
586 if(pStream->Base.Map.pbFile != NULL)
587 free(pStream->Base.Map.pbFile);
588 #endif
589
590 pStream->Base.Map.pbFile = NULL;
591 }
592
593 // Initializes base functions for the mapped file
BaseMap_Init(TFileStream * pStream)594 static void BaseMap_Init(TFileStream * pStream)
595 {
596 // Supply the file stream functions
597 pStream->BaseOpen = BaseMap_Open;
598 pStream->BaseRead = BaseMap_Read;
599 pStream->BaseGetSize = BaseFile_GetSize; // Reuse BaseFile function
600 pStream->BaseGetPos = BaseFile_GetPos; // Reuse BaseFile function
601 pStream->BaseClose = BaseMap_Close;
602
603 // Mapped files are read-only
604 pStream->dwFlags |= STREAM_FLAG_READ_ONLY;
605 }
606
607 //-----------------------------------------------------------------------------
608 // Local functions - base HTTP file support
609
BaseHttp_ExtractServerName(const TCHAR * szFileName,TCHAR * szServerName)610 static const TCHAR * BaseHttp_ExtractServerName(const TCHAR * szFileName, TCHAR * szServerName)
611 {
612 // Check for HTTP
613 if(!_tcsnicmp(szFileName, _T("http://"), 7))
614 szFileName += 7;
615
616 // Cut off the server name
617 if(szServerName != NULL)
618 {
619 while(szFileName[0] != 0 && szFileName[0] != _T('/'))
620 *szServerName++ = *szFileName++;
621 *szServerName = 0;
622 }
623 else
624 {
625 while(szFileName[0] != 0 && szFileName[0] != _T('/'))
626 szFileName++;
627 }
628
629 // Return the remainder
630 return szFileName;
631 }
632
BaseHttp_Open(TFileStream * pStream,const TCHAR * szFileName,DWORD dwStreamFlags)633 static bool BaseHttp_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD dwStreamFlags)
634 {
635 #ifdef PLATFORM_WINDOWS
636
637 HINTERNET hRequest;
638 DWORD dwTemp = 0;
639
640 // Keep compiler happy
641 dwStreamFlags = dwStreamFlags;
642
643 // Don't connect to the internet
644 if(!InternetGetConnectedState(&dwTemp, 0))
645 return false;
646
647 // Initiate the connection to the internet
648 pStream->Base.Http.hInternet = InternetOpen(_T("StormLib HTTP MPQ reader"),
649 INTERNET_OPEN_TYPE_PRECONFIG,
650 NULL,
651 NULL,
652 0);
653 if(pStream->Base.Http.hInternet != NULL)
654 {
655 TCHAR szServerName[MAX_PATH];
656 DWORD dwFlags = INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_UI | INTERNET_FLAG_NO_CACHE_WRITE;
657
658 // Initiate connection with the server
659 szFileName = BaseHttp_ExtractServerName(szFileName, szServerName);
660 pStream->Base.Http.hConnect = InternetConnect(pStream->Base.Http.hInternet,
661 szServerName,
662 INTERNET_DEFAULT_HTTP_PORT,
663 NULL,
664 NULL,
665 INTERNET_SERVICE_HTTP,
666 dwFlags,
667 0);
668 if(pStream->Base.Http.hConnect != NULL)
669 {
670 // Open HTTP request to the file
671 hRequest = HttpOpenRequest(pStream->Base.Http.hConnect, _T("GET"), szFileName, NULL, NULL, NULL, INTERNET_FLAG_NO_CACHE_WRITE, 0);
672 if(hRequest != NULL)
673 {
674 if(HttpSendRequest(hRequest, NULL, 0, NULL, 0))
675 {
676 ULONGLONG FileTime = 0;
677 DWORD dwFileSize = 0;
678 DWORD dwDataSize;
679 DWORD dwIndex = 0;
680
681 // Check if the MPQ has Last Modified field
682 dwDataSize = sizeof(ULONGLONG);
683 if(HttpQueryInfo(hRequest, HTTP_QUERY_LAST_MODIFIED | HTTP_QUERY_FLAG_SYSTEMTIME, &FileTime, &dwDataSize, &dwIndex))
684 pStream->Base.Http.FileTime = FileTime;
685
686 // Verify if the server supports random access
687 dwDataSize = sizeof(DWORD);
688 if(HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &dwFileSize, &dwDataSize, &dwIndex))
689 {
690 if(dwFileSize != 0)
691 {
692 InternetCloseHandle(hRequest);
693 pStream->Base.Http.FileSize = dwFileSize;
694 pStream->Base.Http.FilePos = 0;
695 return true;
696 }
697 }
698 }
699
700 // Close the request
701 InternetCloseHandle(hRequest);
702 }
703
704 // Close the connection handle
705 InternetCloseHandle(pStream->Base.Http.hConnect);
706 pStream->Base.Http.hConnect = NULL;
707 }
708
709 // Close the internet handle
710 InternetCloseHandle(pStream->Base.Http.hInternet);
711 pStream->Base.Http.hInternet = NULL;
712 }
713
714 // If the file is not there or is not available for random access, report error
715 pStream->BaseClose(pStream);
716 return false;
717
718 #else
719
720 // Not supported
721 SetLastError(ERROR_NOT_SUPPORTED);
722 pStream = pStream;
723 return false;
724
725 #endif
726 }
727
BaseHttp_Read(TFileStream * pStream,ULONGLONG * pByteOffset,void * pvBuffer,DWORD dwBytesToRead)728 static bool BaseHttp_Read(
729 TFileStream * pStream, // Pointer to an open stream
730 ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position
731 void * pvBuffer, // Pointer to data to be read
732 DWORD dwBytesToRead) // Number of bytes to read from the file
733 {
734 #ifdef PLATFORM_WINDOWS
735 ULONGLONG ByteOffset = (pByteOffset != NULL) ? *pByteOffset : pStream->Base.Http.FilePos;
736 DWORD dwTotalBytesRead = 0;
737
738 // Do we have to read anything at all?
739 if(dwBytesToRead != 0)
740 {
741 HINTERNET hRequest;
742 LPCTSTR szFileName;
743 LPBYTE pbBuffer = (LPBYTE)pvBuffer;
744 TCHAR szRangeRequest[0x80];
745 DWORD dwStartOffset = (DWORD)ByteOffset;
746 DWORD dwEndOffset = dwStartOffset + dwBytesToRead;
747
748 // Open HTTP request to the file
749 szFileName = BaseHttp_ExtractServerName(pStream->szFileName, NULL);
750 hRequest = HttpOpenRequest(pStream->Base.Http.hConnect, _T("GET"), szFileName, NULL, NULL, NULL, INTERNET_FLAG_NO_CACHE_WRITE, 0);
751 if(hRequest != NULL)
752 {
753 // Add range request to the HTTP headers
754 // http://www.clevercomponents.com/articles/article015/resuming.asp
755 _stprintf(szRangeRequest, _T("Range: bytes=%u-%u"), (unsigned int)dwStartOffset, (unsigned int)dwEndOffset);
756 HttpAddRequestHeaders(hRequest, szRangeRequest, 0xFFFFFFFF, HTTP_ADDREQ_FLAG_ADD_IF_NEW);
757
758 // Send the request to the server
759 if(HttpSendRequest(hRequest, NULL, 0, NULL, 0))
760 {
761 while(dwTotalBytesRead < dwBytesToRead)
762 {
763 DWORD dwBlockBytesToRead = dwBytesToRead - dwTotalBytesRead;
764 DWORD dwBlockBytesRead = 0;
765
766 // Read the block from the file
767 if(dwBlockBytesToRead > 0x200)
768 dwBlockBytesToRead = 0x200;
769 InternetReadFile(hRequest, pbBuffer, dwBlockBytesToRead, &dwBlockBytesRead);
770
771 // Check for end
772 if(dwBlockBytesRead == 0)
773 break;
774
775 // Move buffers
776 dwTotalBytesRead += dwBlockBytesRead;
777 pbBuffer += dwBlockBytesRead;
778 }
779 }
780 InternetCloseHandle(hRequest);
781 }
782 }
783
784 // Increment the current file position by number of bytes read
785 pStream->Base.Http.FilePos = ByteOffset + dwTotalBytesRead;
786
787 // If the number of bytes read doesn't match the required amount, return false
788 if(dwTotalBytesRead != dwBytesToRead)
789 SetLastError(ERROR_HANDLE_EOF);
790 return (dwTotalBytesRead == dwBytesToRead);
791
792 #else
793
794 // Not supported
795 pStream = pStream;
796 pByteOffset = pByteOffset;
797 pvBuffer = pvBuffer;
798 dwBytesToRead = dwBytesToRead;
799 SetLastError(ERROR_NOT_SUPPORTED);
800 return false;
801
802 #endif
803 }
804
BaseHttp_Close(TFileStream * pStream)805 static void BaseHttp_Close(TFileStream * pStream)
806 {
807 #ifdef PLATFORM_WINDOWS
808 if(pStream->Base.Http.hConnect != NULL)
809 InternetCloseHandle(pStream->Base.Http.hConnect);
810 pStream->Base.Http.hConnect = NULL;
811
812 if(pStream->Base.Http.hInternet != NULL)
813 InternetCloseHandle(pStream->Base.Http.hInternet);
814 pStream->Base.Http.hInternet = NULL;
815 #else
816 pStream = pStream;
817 #endif
818 }
819
820 // Initializes base functions for the mapped file
BaseHttp_Init(TFileStream * pStream)821 static void BaseHttp_Init(TFileStream * pStream)
822 {
823 // Supply the stream functions
824 pStream->BaseOpen = BaseHttp_Open;
825 pStream->BaseRead = BaseHttp_Read;
826 pStream->BaseGetSize = BaseFile_GetSize; // Reuse BaseFile function
827 pStream->BaseGetPos = BaseFile_GetPos; // Reuse BaseFile function
828 pStream->BaseClose = BaseHttp_Close;
829
830 // HTTP files are read-only
831 pStream->dwFlags |= STREAM_FLAG_READ_ONLY;
832 }
833
834 //-----------------------------------------------------------------------------
835 // Local functions - base block-based support
836
837 // Generic function that loads blocks from the file
838 // The function groups the block with the same availability,
839 // so the called BlockRead can finish the request in a single system call
BlockStream_Read(TBlockStream * pStream,ULONGLONG * pByteOffset,void * pvBuffer,DWORD dwBytesToRead)840 static bool BlockStream_Read(
841 TBlockStream * pStream, // Pointer to an open stream
842 ULONGLONG * pByteOffset, // Pointer to file byte offset. If NULL, it reads from the current position
843 void * pvBuffer, // Pointer to data to be read
844 DWORD dwBytesToRead) // Number of bytes to read from the file
845 {
846 ULONGLONG BlockOffset0;
847 ULONGLONG BlockOffset;
848 ULONGLONG ByteOffset;
849 ULONGLONG EndOffset;
850 LPBYTE TransferBuffer;
851 LPBYTE BlockBuffer;
852 DWORD BlockBufferOffset; // Offset of the desired data in the block buffer
853 DWORD BytesNeeded; // Number of bytes that really need to be read
854 DWORD BlockSize = pStream->BlockSize;
855 DWORD BlockCount;
856 bool bPrevBlockAvailable;
857 bool bCallbackCalled = false;
858 bool bBlockAvailable;
859 bool bResult = true;
860
861 // The base block read function must be present
862 assert(pStream->BlockRead != NULL);
863
864 // NOP reading of zero bytes
865 if(dwBytesToRead == 0)
866 return true;
867
868 // Get the current position in the stream
869 ByteOffset = (pByteOffset != NULL) ? pByteOffset[0] : pStream->StreamPos;
870 EndOffset = ByteOffset + dwBytesToRead;
871 if(EndOffset > pStream->StreamSize)
872 {
873 SetLastError(ERROR_HANDLE_EOF);
874 return false;
875 }
876
877 // Calculate the block parameters
878 BlockOffset0 = BlockOffset = ByteOffset & ~((ULONGLONG)BlockSize - 1);
879 BlockCount = (DWORD)(((EndOffset - BlockOffset) + (BlockSize - 1)) / BlockSize);
880 BytesNeeded = (DWORD)(EndOffset - BlockOffset);
881
882 // Remember where we have our data
883 assert((BlockSize & (BlockSize - 1)) == 0);
884 BlockBufferOffset = (DWORD)(ByteOffset & (BlockSize - 1));
885
886 // Allocate buffer for reading blocks
887 TransferBuffer = BlockBuffer = STORM_ALLOC(BYTE, (BlockCount * BlockSize));
888 if(TransferBuffer == NULL)
889 {
890 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
891 return false;
892 }
893
894 // If all blocks are available, just read all blocks at once
895 if(pStream->IsComplete == 0)
896 {
897 // Now parse the blocks and send the block read request
898 // to all blocks with the same availability
899 assert(pStream->BlockCheck != NULL);
900 bPrevBlockAvailable = pStream->BlockCheck(pStream, BlockOffset);
901
902 // Loop as long as we have something to read
903 while(BlockOffset < EndOffset)
904 {
905 // Determine availability of the next block
906 bBlockAvailable = pStream->BlockCheck(pStream, BlockOffset);
907
908 // If the availability has changed, read all blocks up to this one
909 if(bBlockAvailable != bPrevBlockAvailable)
910 {
911 // Call the file stream callback, if the block is not available
912 if(pStream->pMaster && pStream->pfnCallback && bPrevBlockAvailable == false)
913 {
914 pStream->pfnCallback(pStream->UserData, BlockOffset0, (DWORD)(BlockOffset - BlockOffset0));
915 bCallbackCalled = true;
916 }
917
918 // Load the continuous blocks with the same availability
919 assert(BlockOffset > BlockOffset0);
920 bResult = pStream->BlockRead(pStream, BlockOffset0, BlockOffset, BlockBuffer, BytesNeeded, bPrevBlockAvailable);
921 if(!bResult)
922 break;
923
924 // Move the block offset
925 BlockBuffer += (DWORD)(BlockOffset - BlockOffset0);
926 BytesNeeded -= (DWORD)(BlockOffset - BlockOffset0);
927 bPrevBlockAvailable = bBlockAvailable;
928 BlockOffset0 = BlockOffset;
929 }
930
931 // Move to the block offset in the stream
932 BlockOffset += BlockSize;
933 }
934
935 // If there is a block(s) remaining to be read, do it
936 if(BlockOffset > BlockOffset0)
937 {
938 // Call the file stream callback, if the block is not available
939 if(pStream->pMaster && pStream->pfnCallback && bPrevBlockAvailable == false)
940 {
941 pStream->pfnCallback(pStream->UserData, BlockOffset0, (DWORD)(BlockOffset - BlockOffset0));
942 bCallbackCalled = true;
943 }
944
945 // Read the complete blocks from the file
946 if(BlockOffset > pStream->StreamSize)
947 BlockOffset = pStream->StreamSize;
948 bResult = pStream->BlockRead(pStream, BlockOffset0, BlockOffset, BlockBuffer, BytesNeeded, bPrevBlockAvailable);
949 }
950 }
951 else
952 {
953 // Read the complete blocks from the file
954 if(EndOffset > pStream->StreamSize)
955 EndOffset = pStream->StreamSize;
956 bResult = pStream->BlockRead(pStream, BlockOffset, EndOffset, BlockBuffer, BytesNeeded, true);
957 }
958
959 // Now copy the data to the user buffer
960 if(bResult)
961 {
962 memcpy(pvBuffer, TransferBuffer + BlockBufferOffset, dwBytesToRead);
963 pStream->StreamPos = ByteOffset + dwBytesToRead;
964 }
965 else
966 {
967 // If the block read failed, set the last error
968 SetLastError(ERROR_FILE_INCOMPLETE);
969 }
970
971 // Call the callback to indicate we are done
972 if(bCallbackCalled)
973 pStream->pfnCallback(pStream->UserData, 0, 0);
974
975 // Free the block buffer and return
976 STORM_FREE(TransferBuffer);
977 return bResult;
978 }
979
BlockStream_GetSize(TFileStream * pStream,ULONGLONG * pFileSize)980 static bool BlockStream_GetSize(TFileStream * pStream, ULONGLONG * pFileSize)
981 {
982 *pFileSize = pStream->StreamSize;
983 return true;
984 }
985
BlockStream_GetPos(TFileStream * pStream,ULONGLONG * pByteOffset)986 static bool BlockStream_GetPos(TFileStream * pStream, ULONGLONG * pByteOffset)
987 {
988 *pByteOffset = pStream->StreamPos;
989 return true;
990 }
991
BlockStream_Close(TBlockStream * pStream)992 static void BlockStream_Close(TBlockStream * pStream)
993 {
994 // Free the data map, if any
995 if(pStream->FileBitmap != NULL)
996 STORM_FREE(pStream->FileBitmap);
997 pStream->FileBitmap = NULL;
998
999 // Call the base class for closing the stream
1000 pStream->BaseClose(pStream);
1001 }
1002
1003 //-----------------------------------------------------------------------------
1004 // File stream allocation function
1005
1006 static STREAM_INIT StreamBaseInit[4] =
1007 {
1008 BaseFile_Init,
1009 BaseMap_Init,
1010 BaseHttp_Init,
1011 BaseNone_Init
1012 };
1013
1014 // This function allocates an empty structure for the file stream
1015 // The stream structure is created as flat block, variable length
1016 // The file name is placed after the end of the stream structure data
AllocateFileStream(const TCHAR * szFileName,size_t StreamSize,DWORD dwStreamFlags)1017 static TFileStream * AllocateFileStream(
1018 const TCHAR * szFileName,
1019 size_t StreamSize,
1020 DWORD dwStreamFlags)
1021 {
1022 TFileStream * pMaster = NULL;
1023 TFileStream * pStream;
1024 const TCHAR * szNextFile = szFileName;
1025 size_t FileNameSize;
1026
1027 // Sanity check
1028 assert(StreamSize != 0);
1029
1030 // The caller can specify chain of files in the following form:
1031 // C:\archive.MPQ*http://www.server.com/MPQs/archive-server.MPQ
1032 // In that case, we use the part after "*" as master file name
1033 while(szNextFile[0] != 0 && szNextFile[0] != _T('*'))
1034 szNextFile++;
1035 FileNameSize = (size_t)((szNextFile - szFileName) * sizeof(TCHAR));
1036
1037 // If we have a next file, we need to open it as master stream
1038 // Note that we don't care if the master stream exists or not,
1039 // If it doesn't, later attempts to read missing file block will fail
1040 if(szNextFile[0] == _T('*'))
1041 {
1042 // Don't allow another master file in the string
1043 if(_tcschr(szNextFile + 1, _T('*')) != NULL)
1044 {
1045 SetLastError(ERROR_INVALID_PARAMETER);
1046 return NULL;
1047 }
1048
1049 // Open the master file
1050 pMaster = FileStream_OpenFile(szNextFile + 1, STREAM_FLAG_READ_ONLY);
1051 }
1052
1053 // Allocate the stream structure for the given stream type
1054 pStream = (TFileStream *)STORM_ALLOC(BYTE, StreamSize + FileNameSize + sizeof(TCHAR));
1055 if(pStream != NULL)
1056 {
1057 // Zero the entire structure
1058 memset(pStream, 0, StreamSize);
1059 pStream->pMaster = pMaster;
1060 pStream->dwFlags = dwStreamFlags;
1061
1062 // Initialize the file name
1063 pStream->szFileName = (TCHAR *)((BYTE *)pStream + StreamSize);
1064 memcpy(pStream->szFileName, szFileName, FileNameSize);
1065 pStream->szFileName[FileNameSize / sizeof(TCHAR)] = 0;
1066
1067 // Initialize the stream functions
1068 StreamBaseInit[dwStreamFlags & 0x03](pStream);
1069 }
1070
1071 return pStream;
1072 }
1073
1074 //-----------------------------------------------------------------------------
1075 // Local functions - flat stream support
1076
FlatStream_CheckFile(TBlockStream * pStream)1077 static DWORD FlatStream_CheckFile(TBlockStream * pStream)
1078 {
1079 LPBYTE FileBitmap = (LPBYTE)pStream->FileBitmap;
1080 DWORD WholeByteCount = (pStream->BlockCount / 8);
1081 DWORD ExtraBitsCount = (pStream->BlockCount & 7);
1082 BYTE ExpectedValue;
1083
1084 // Verify the whole bytes - their value must be 0xFF
1085 for(DWORD i = 0; i < WholeByteCount; i++)
1086 {
1087 if(FileBitmap[i] != 0xFF)
1088 return 0;
1089 }
1090
1091 // If there are extra bits, calculate the mask
1092 if(ExtraBitsCount != 0)
1093 {
1094 ExpectedValue = (BYTE)((1 << ExtraBitsCount) - 1);
1095 if(FileBitmap[WholeByteCount] != ExpectedValue)
1096 return 0;
1097 }
1098
1099 // Yes, the file is complete
1100 return 1;
1101 }
1102
FlatStream_LoadBitmap(TBlockStream * pStream)1103 static bool FlatStream_LoadBitmap(TBlockStream * pStream)
1104 {
1105 FILE_BITMAP_FOOTER Footer;
1106 ULONGLONG ByteOffset;
1107 LPBYTE FileBitmap;
1108 DWORD BlockCount;
1109 DWORD BitmapSize;
1110
1111 // Do not load the bitmap if we should not have to
1112 if(!(pStream->dwFlags & STREAM_FLAG_USE_BITMAP))
1113 return false;
1114
1115 // Only if the size is greater than size of bitmap footer
1116 if(pStream->Base.File.FileSize > sizeof(FILE_BITMAP_FOOTER))
1117 {
1118 // Load the bitmap footer
1119 ByteOffset = pStream->Base.File.FileSize - sizeof(FILE_BITMAP_FOOTER);
1120 if(pStream->BaseRead(pStream, &ByteOffset, &Footer, sizeof(FILE_BITMAP_FOOTER)))
1121 {
1122 // Make sure that the array is properly BSWAP-ed
1123 BSWAP_ARRAY32_UNSIGNED((LPDWORD)(&Footer), sizeof(FILE_BITMAP_FOOTER));
1124
1125 // Verify if there is actually a footer
1126 if(Footer.Signature == ID_FILE_BITMAP_FOOTER && Footer.Version == 0x03)
1127 {
1128 // Get the offset of the bitmap, number of blocks and size of the bitmap
1129 ByteOffset = MAKE_OFFSET64(Footer.MapOffsetHi, Footer.MapOffsetLo);
1130 BlockCount = (DWORD)(((ByteOffset - 1) / Footer.BlockSize) + 1);
1131 BitmapSize = ((BlockCount + 7) / 8);
1132
1133 // Check if the sizes match
1134 if(ByteOffset + BitmapSize + sizeof(FILE_BITMAP_FOOTER) == pStream->Base.File.FileSize)
1135 {
1136 // Allocate space for the bitmap
1137 FileBitmap = STORM_ALLOC(BYTE, BitmapSize);
1138 if(FileBitmap != NULL)
1139 {
1140 // Load the bitmap bits
1141 if(!pStream->BaseRead(pStream, &ByteOffset, FileBitmap, BitmapSize))
1142 {
1143 STORM_FREE(FileBitmap);
1144 return false;
1145 }
1146
1147 // Update the stream size
1148 pStream->BuildNumber = Footer.BuildNumber;
1149 pStream->StreamSize = ByteOffset;
1150
1151 // Fill the bitmap information
1152 pStream->FileBitmap = FileBitmap;
1153 pStream->BitmapSize = BitmapSize;
1154 pStream->BlockSize = Footer.BlockSize;
1155 pStream->BlockCount = BlockCount;
1156 pStream->IsComplete = FlatStream_CheckFile(pStream);
1157 return true;
1158 }
1159 }
1160 }
1161 }
1162 }
1163
1164 return false;
1165 }
1166
FlatStream_UpdateBitmap(TBlockStream * pStream,ULONGLONG StartOffset,ULONGLONG EndOffset)1167 static void FlatStream_UpdateBitmap(
1168 TBlockStream * pStream, // Pointer to an open stream
1169 ULONGLONG StartOffset,
1170 ULONGLONG EndOffset)
1171 {
1172 LPBYTE FileBitmap = (LPBYTE)pStream->FileBitmap;
1173 DWORD BlockIndex;
1174 DWORD BlockSize = pStream->BlockSize;
1175 DWORD ByteIndex;
1176 BYTE BitMask;
1177
1178 // Sanity checks
1179 assert((StartOffset & (BlockSize - 1)) == 0);
1180 assert(FileBitmap != NULL);
1181
1182 // Calculate the index of the block
1183 BlockIndex = (DWORD)(StartOffset / BlockSize);
1184 ByteIndex = (BlockIndex / 0x08);
1185 BitMask = (BYTE)(1 << (BlockIndex & 0x07));
1186
1187 // Set all bits for the specified range
1188 while(StartOffset < EndOffset)
1189 {
1190 // Set the bit
1191 FileBitmap[ByteIndex] |= BitMask;
1192
1193 // Move all
1194 StartOffset += BlockSize;
1195 ByteIndex += (BitMask >> 0x07);
1196 BitMask = (BitMask >> 0x07) | (BitMask << 0x01);
1197 }
1198
1199 // Increment the bitmap update count
1200 pStream->IsModified = 1;
1201 }
1202
FlatStream_BlockCheck(TBlockStream * pStream,ULONGLONG BlockOffset)1203 static bool FlatStream_BlockCheck(
1204 TBlockStream * pStream, // Pointer to an open stream
1205 ULONGLONG BlockOffset)
1206 {
1207 LPBYTE FileBitmap = (LPBYTE)pStream->FileBitmap;
1208 DWORD BlockIndex;
1209 BYTE BitMask;
1210
1211 // Sanity checks
1212 assert((BlockOffset & (pStream->BlockSize - 1)) == 0);
1213 assert(FileBitmap != NULL);
1214
1215 // Calculate the index of the block
1216 BlockIndex = (DWORD)(BlockOffset / pStream->BlockSize);
1217 BitMask = (BYTE)(1 << (BlockIndex & 0x07));
1218
1219 // Check if the bit is present
1220 return (FileBitmap[BlockIndex / 0x08] & BitMask) ? true : false;
1221 }
1222
FlatStream_BlockRead(TBlockStream * pStream,ULONGLONG StartOffset,ULONGLONG EndOffset,LPBYTE BlockBuffer,DWORD BytesNeeded,bool bAvailable)1223 static bool FlatStream_BlockRead(
1224 TBlockStream * pStream, // Pointer to an open stream
1225 ULONGLONG StartOffset,
1226 ULONGLONG EndOffset,
1227 LPBYTE BlockBuffer,
1228 DWORD BytesNeeded,
1229 bool bAvailable)
1230 {
1231 DWORD BytesToRead = (DWORD)(EndOffset - StartOffset);
1232
1233 // The starting offset must be aligned to size of the block
1234 assert(pStream->FileBitmap != NULL);
1235 assert((StartOffset & (pStream->BlockSize - 1)) == 0);
1236 assert(StartOffset < EndOffset);
1237
1238 // If the blocks are not available, we need to load them from the master
1239 // and then save to the mirror
1240 if(bAvailable == false)
1241 {
1242 // If we have no master, we cannot satisfy read request
1243 if(pStream->pMaster == NULL)
1244 return false;
1245
1246 // Load the blocks from the master stream
1247 // Note that we always have to read complete blocks
1248 // so they get properly stored to the mirror stream
1249 if(!FileStream_Read(pStream->pMaster, &StartOffset, BlockBuffer, BytesToRead))
1250 return false;
1251
1252 // Store the loaded blocks to the mirror file.
1253 // Note that this operation is not required to succeed
1254 if(pStream->BaseWrite(pStream, &StartOffset, BlockBuffer, BytesToRead))
1255 FlatStream_UpdateBitmap(pStream, StartOffset, EndOffset);
1256
1257 return true;
1258 }
1259 else
1260 {
1261 if(BytesToRead > BytesNeeded)
1262 BytesToRead = BytesNeeded;
1263 return pStream->BaseRead(pStream, &StartOffset, BlockBuffer, BytesToRead);
1264 }
1265 }
1266
FlatStream_Close(TBlockStream * pStream)1267 static void FlatStream_Close(TBlockStream * pStream)
1268 {
1269 FILE_BITMAP_FOOTER Footer;
1270
1271 if(pStream->FileBitmap && pStream->IsModified)
1272 {
1273 // Write the file bitmap
1274 pStream->BaseWrite(pStream, &pStream->StreamSize, pStream->FileBitmap, pStream->BitmapSize);
1275
1276 // Prepare and write the file footer
1277 Footer.Signature = ID_FILE_BITMAP_FOOTER;
1278 Footer.Version = 3;
1279 Footer.BuildNumber = pStream->BuildNumber;
1280 Footer.MapOffsetLo = (DWORD)(pStream->StreamSize & 0xFFFFFFFF);
1281 Footer.MapOffsetHi = (DWORD)(pStream->StreamSize >> 0x20);
1282 Footer.BlockSize = pStream->BlockSize;
1283 BSWAP_ARRAY32_UNSIGNED(&Footer, sizeof(FILE_BITMAP_FOOTER));
1284 pStream->BaseWrite(pStream, NULL, &Footer, sizeof(FILE_BITMAP_FOOTER));
1285 }
1286
1287 // Close the base class
1288 BlockStream_Close(pStream);
1289 }
1290
FlatStream_CreateMirror(TBlockStream * pStream)1291 static bool FlatStream_CreateMirror(TBlockStream * pStream)
1292 {
1293 ULONGLONG MasterSize = 0;
1294 ULONGLONG MirrorSize = 0;
1295 LPBYTE FileBitmap = NULL;
1296 DWORD dwBitmapSize;
1297 DWORD dwBlockCount;
1298 bool bNeedCreateMirrorStream = true;
1299 bool bNeedResizeMirrorStream = true;
1300
1301 // Do we have master function and base creation function?
1302 if(pStream->pMaster == NULL || pStream->BaseCreate == NULL)
1303 return false;
1304
1305 // Retrieve the master file size, block count and bitmap size
1306 FileStream_GetSize(pStream->pMaster, &MasterSize);
1307 dwBlockCount = (DWORD)((MasterSize + DEFAULT_BLOCK_SIZE - 1) / DEFAULT_BLOCK_SIZE);
1308 dwBitmapSize = (DWORD)((dwBlockCount + 7) / 8);
1309
1310 // Setup stream size and position
1311 pStream->BuildNumber = DEFAULT_BUILD_NUMBER; // BUGBUG: Really???
1312 pStream->StreamSize = MasterSize;
1313 pStream->StreamPos = 0;
1314
1315 // Open the base stream for write access
1316 if(pStream->BaseOpen(pStream, pStream->szFileName, 0))
1317 {
1318 // If the file open succeeded, check if the file size matches required size
1319 pStream->BaseGetSize(pStream, &MirrorSize);
1320 if(MirrorSize == MasterSize + dwBitmapSize + sizeof(FILE_BITMAP_FOOTER))
1321 {
1322 // Attempt to load an existing file bitmap
1323 if(FlatStream_LoadBitmap(pStream))
1324 return true;
1325
1326 // We need to create new file bitmap
1327 bNeedResizeMirrorStream = false;
1328 }
1329
1330 // We need to create mirror stream
1331 bNeedCreateMirrorStream = false;
1332 }
1333
1334 // Create a new stream, if needed
1335 if(bNeedCreateMirrorStream)
1336 {
1337 if(!pStream->BaseCreate(pStream))
1338 return false;
1339 }
1340
1341 // If we need to, then resize the mirror stream
1342 if(bNeedResizeMirrorStream)
1343 {
1344 if(!pStream->BaseResize(pStream, MasterSize + dwBitmapSize + sizeof(FILE_BITMAP_FOOTER)))
1345 return false;
1346 }
1347
1348 // Allocate the bitmap array
1349 FileBitmap = STORM_ALLOC(BYTE, dwBitmapSize);
1350 if(FileBitmap == NULL)
1351 return false;
1352
1353 // Initialize the bitmap
1354 memset(FileBitmap, 0, dwBitmapSize);
1355 pStream->FileBitmap = FileBitmap;
1356 pStream->BitmapSize = dwBitmapSize;
1357 pStream->BlockSize = DEFAULT_BLOCK_SIZE;
1358 pStream->BlockCount = dwBlockCount;
1359 pStream->IsComplete = 0;
1360 pStream->IsModified = 1;
1361
1362 // Note: Don't write the stream bitmap right away.
1363 // Doing so would cause sparse file resize on NTFS,
1364 // which would take long time on larger files.
1365 return true;
1366 }
1367
FlatStream_Open(const TCHAR * szFileName,DWORD dwStreamFlags)1368 static TFileStream * FlatStream_Open(const TCHAR * szFileName, DWORD dwStreamFlags)
1369 {
1370 TBlockStream * pStream;
1371 ULONGLONG ByteOffset = 0;
1372
1373 // Create new empty stream
1374 pStream = (TBlockStream *)AllocateFileStream(szFileName, sizeof(TBlockStream), dwStreamFlags);
1375 if(pStream == NULL)
1376 return NULL;
1377
1378 // Do we have a master stream?
1379 if(pStream->pMaster != NULL)
1380 {
1381 if(!FlatStream_CreateMirror(pStream))
1382 {
1383 FileStream_Close(pStream);
1384 SetLastError(ERROR_FILE_NOT_FOUND);
1385 return NULL;
1386 }
1387 }
1388 else
1389 {
1390 // Attempt to open the base stream
1391 if(!pStream->BaseOpen(pStream, pStream->szFileName, dwStreamFlags))
1392 {
1393 FileStream_Close(pStream);
1394 return NULL;
1395 }
1396
1397 // Load the bitmap, if required to
1398 if(dwStreamFlags & STREAM_FLAG_USE_BITMAP)
1399 FlatStream_LoadBitmap(pStream);
1400 }
1401
1402 // If we have a stream bitmap, set the reading functions
1403 // which check presence of each file block
1404 if(pStream->FileBitmap != NULL)
1405 {
1406 // Set the stream position to zero. Stream size is already set
1407 assert(pStream->StreamSize != 0);
1408 pStream->StreamPos = 0;
1409 pStream->dwFlags |= STREAM_FLAG_READ_ONLY;
1410
1411 // Supply the stream functions
1412 pStream->StreamRead = (STREAM_READ)BlockStream_Read;
1413 pStream->StreamGetSize = BlockStream_GetSize;
1414 pStream->StreamGetPos = BlockStream_GetPos;
1415 pStream->StreamClose = (STREAM_CLOSE)FlatStream_Close;
1416
1417 // Supply the block functions
1418 pStream->BlockCheck = (BLOCK_CHECK)FlatStream_BlockCheck;
1419 pStream->BlockRead = (BLOCK_READ)FlatStream_BlockRead;
1420 }
1421 else
1422 {
1423 // Reset the base position to zero
1424 pStream->BaseRead(pStream, &ByteOffset, NULL, 0);
1425
1426 // Setup stream size and position
1427 pStream->StreamSize = pStream->Base.File.FileSize;
1428 pStream->StreamPos = 0;
1429
1430 // Set the base functions
1431 pStream->StreamRead = pStream->BaseRead;
1432 pStream->StreamWrite = pStream->BaseWrite;
1433 pStream->StreamResize = pStream->BaseResize;
1434 pStream->StreamGetSize = pStream->BaseGetSize;
1435 pStream->StreamGetPos = pStream->BaseGetPos;
1436 pStream->StreamClose = pStream->BaseClose;
1437 }
1438
1439 return pStream;
1440 }
1441
1442 //-----------------------------------------------------------------------------
1443 // Local functions - partial stream support
1444
IsPartHeader(PPART_FILE_HEADER pPartHdr)1445 static bool IsPartHeader(PPART_FILE_HEADER pPartHdr)
1446 {
1447 // Version number must be 2
1448 if(pPartHdr->PartialVersion == 2)
1449 {
1450 // GameBuildNumber must be an ASCII number
1451 if(isdigit(pPartHdr->GameBuildNumber[0]) && isdigit(pPartHdr->GameBuildNumber[1]) && isdigit(pPartHdr->GameBuildNumber[2]))
1452 {
1453 // Block size must be power of 2
1454 if((pPartHdr->BlockSize & (pPartHdr->BlockSize - 1)) == 0)
1455 return true;
1456 }
1457 }
1458
1459 return false;
1460 }
1461
PartStream_CheckFile(TBlockStream * pStream)1462 static DWORD PartStream_CheckFile(TBlockStream * pStream)
1463 {
1464 PPART_FILE_MAP_ENTRY FileBitmap = (PPART_FILE_MAP_ENTRY)pStream->FileBitmap;
1465 DWORD dwBlockCount;
1466
1467 // Get the number of blocks
1468 dwBlockCount = (DWORD)((pStream->StreamSize + pStream->BlockSize - 1) / pStream->BlockSize);
1469
1470 // Check all blocks
1471 for(DWORD i = 0; i < dwBlockCount; i++, FileBitmap++)
1472 {
1473 // Few sanity checks
1474 assert(FileBitmap->LargeValueHi == 0);
1475 assert(FileBitmap->LargeValueLo == 0);
1476 assert(FileBitmap->Flags == 0 || FileBitmap->Flags == 3);
1477
1478 // Check if this block is present
1479 if(FileBitmap->Flags != 3)
1480 return 0;
1481 }
1482
1483 // Yes, the file is complete
1484 return 1;
1485 }
1486
PartStream_LoadBitmap(TBlockStream * pStream)1487 static bool PartStream_LoadBitmap(TBlockStream * pStream)
1488 {
1489 PPART_FILE_MAP_ENTRY FileBitmap;
1490 PART_FILE_HEADER PartHdr;
1491 ULONGLONG ByteOffset = 0;
1492 ULONGLONG StreamSize = 0;
1493 DWORD BlockCount;
1494 DWORD BitmapSize;
1495
1496 // Only if the size is greater than size of the bitmap header
1497 if(pStream->Base.File.FileSize > sizeof(PART_FILE_HEADER))
1498 {
1499 // Attempt to read PART file header
1500 if(pStream->BaseRead(pStream, &ByteOffset, &PartHdr, sizeof(PART_FILE_HEADER)))
1501 {
1502 // We need to swap PART file header on big-endian platforms
1503 BSWAP_ARRAY32_UNSIGNED(&PartHdr, sizeof(PART_FILE_HEADER));
1504
1505 // Verify the PART file header
1506 if(IsPartHeader(&PartHdr))
1507 {
1508 // Get the number of blocks and size of one block
1509 StreamSize = MAKE_OFFSET64(PartHdr.FileSizeHi, PartHdr.FileSizeLo);
1510 ByteOffset = sizeof(PART_FILE_HEADER);
1511 BlockCount = (DWORD)((StreamSize + PartHdr.BlockSize - 1) / PartHdr.BlockSize);
1512 BitmapSize = BlockCount * sizeof(PART_FILE_MAP_ENTRY);
1513
1514 // Check if sizes match
1515 if((ByteOffset + BitmapSize) < pStream->Base.File.FileSize)
1516 {
1517 // Allocate space for the array of PART_FILE_MAP_ENTRY
1518 FileBitmap = STORM_ALLOC(PART_FILE_MAP_ENTRY, BlockCount);
1519 if(FileBitmap != NULL)
1520 {
1521 // Load the block map
1522 if(!pStream->BaseRead(pStream, &ByteOffset, FileBitmap, BitmapSize))
1523 {
1524 STORM_FREE(FileBitmap);
1525 return false;
1526 }
1527
1528 // Make sure that the byte order is correct
1529 BSWAP_ARRAY32_UNSIGNED(FileBitmap, BitmapSize);
1530
1531 // Update the stream size
1532 pStream->BuildNumber = StringToInt(PartHdr.GameBuildNumber);
1533 pStream->StreamSize = StreamSize;
1534
1535 // Fill the bitmap information
1536 pStream->FileBitmap = FileBitmap;
1537 pStream->BitmapSize = BitmapSize;
1538 pStream->BlockSize = PartHdr.BlockSize;
1539 pStream->BlockCount = BlockCount;
1540 pStream->IsComplete = PartStream_CheckFile(pStream);
1541 return true;
1542 }
1543 }
1544 }
1545 }
1546 }
1547
1548 return false;
1549 }
1550
PartStream_UpdateBitmap(TBlockStream * pStream,ULONGLONG StartOffset,ULONGLONG EndOffset,ULONGLONG RealOffset)1551 static void PartStream_UpdateBitmap(
1552 TBlockStream * pStream, // Pointer to an open stream
1553 ULONGLONG StartOffset,
1554 ULONGLONG EndOffset,
1555 ULONGLONG RealOffset)
1556 {
1557 PPART_FILE_MAP_ENTRY FileBitmap;
1558 DWORD BlockSize = pStream->BlockSize;
1559
1560 // Sanity checks
1561 assert((StartOffset & (BlockSize - 1)) == 0);
1562 assert(pStream->FileBitmap != NULL);
1563
1564 // Calculate the first entry in the block map
1565 FileBitmap = (PPART_FILE_MAP_ENTRY)pStream->FileBitmap + (StartOffset / BlockSize);
1566
1567 // Set all bits for the specified range
1568 while(StartOffset < EndOffset)
1569 {
1570 // Set the bit
1571 FileBitmap->BlockOffsHi = (DWORD)(RealOffset >> 0x20);
1572 FileBitmap->BlockOffsLo = (DWORD)(RealOffset & 0xFFFFFFFF);
1573 FileBitmap->Flags = 3;
1574
1575 // Move all
1576 StartOffset += BlockSize;
1577 RealOffset += BlockSize;
1578 FileBitmap++;
1579 }
1580
1581 // Increment the bitmap update count
1582 pStream->IsModified = 1;
1583 }
1584
PartStream_BlockCheck(TBlockStream * pStream,ULONGLONG BlockOffset)1585 static bool PartStream_BlockCheck(
1586 TBlockStream * pStream, // Pointer to an open stream
1587 ULONGLONG BlockOffset)
1588 {
1589 PPART_FILE_MAP_ENTRY FileBitmap;
1590
1591 // Sanity checks
1592 assert((BlockOffset & (pStream->BlockSize - 1)) == 0);
1593 assert(pStream->FileBitmap != NULL);
1594
1595 // Calculate the block map entry
1596 FileBitmap = (PPART_FILE_MAP_ENTRY)pStream->FileBitmap + (BlockOffset / pStream->BlockSize);
1597
1598 // Check if the flags are present
1599 return (FileBitmap->Flags & 0x03) ? true : false;
1600 }
1601
PartStream_BlockRead(TBlockStream * pStream,ULONGLONG StartOffset,ULONGLONG EndOffset,LPBYTE BlockBuffer,DWORD BytesNeeded,bool bAvailable)1602 static bool PartStream_BlockRead(
1603 TBlockStream * pStream,
1604 ULONGLONG StartOffset,
1605 ULONGLONG EndOffset,
1606 LPBYTE BlockBuffer,
1607 DWORD BytesNeeded,
1608 bool bAvailable)
1609 {
1610 PPART_FILE_MAP_ENTRY FileBitmap;
1611 ULONGLONG ByteOffset;
1612 DWORD BytesToRead;
1613 DWORD BlockIndex = (DWORD)(StartOffset / pStream->BlockSize);
1614
1615 // The starting offset must be aligned to size of the block
1616 assert(pStream->FileBitmap != NULL);
1617 assert((StartOffset & (pStream->BlockSize - 1)) == 0);
1618 assert(StartOffset < EndOffset);
1619
1620 // If the blocks are not available, we need to load them from the master
1621 // and then save to the mirror
1622 if(bAvailable == false)
1623 {
1624 // If we have no master, we cannot satisfy read request
1625 if(pStream->pMaster == NULL)
1626 return false;
1627
1628 // Load the blocks from the master stream
1629 // Note that we always have to read complete blocks
1630 // so they get properly stored to the mirror stream
1631 BytesToRead = (DWORD)(EndOffset - StartOffset);
1632 if(!FileStream_Read(pStream->pMaster, &StartOffset, BlockBuffer, BytesToRead))
1633 return false;
1634
1635 // The loaded blocks are going to be stored to the end of the file
1636 // Note that this operation is not required to succeed
1637 if(pStream->BaseGetSize(pStream, &ByteOffset))
1638 {
1639 // Store the loaded blocks to the mirror file.
1640 if(pStream->BaseWrite(pStream, &ByteOffset, BlockBuffer, BytesToRead))
1641 {
1642 PartStream_UpdateBitmap(pStream, StartOffset, EndOffset, ByteOffset);
1643 }
1644 }
1645 }
1646 else
1647 {
1648 // Get the file map entry
1649 FileBitmap = (PPART_FILE_MAP_ENTRY)pStream->FileBitmap + BlockIndex;
1650
1651 // Read all blocks
1652 while(StartOffset < EndOffset)
1653 {
1654 // Get the number of bytes to be read
1655 BytesToRead = (DWORD)(EndOffset - StartOffset);
1656 if(BytesToRead > pStream->BlockSize)
1657 BytesToRead = pStream->BlockSize;
1658 if(BytesToRead > BytesNeeded)
1659 BytesToRead = BytesNeeded;
1660
1661 // Read the block
1662 ByteOffset = MAKE_OFFSET64(FileBitmap->BlockOffsHi, FileBitmap->BlockOffsLo);
1663 if(!pStream->BaseRead(pStream, &ByteOffset, BlockBuffer, BytesToRead))
1664 return false;
1665
1666 // Move the pointers
1667 StartOffset += pStream->BlockSize;
1668 BlockBuffer += pStream->BlockSize;
1669 BytesNeeded -= pStream->BlockSize;
1670 FileBitmap++;
1671 }
1672 }
1673
1674 return true;
1675 }
1676
PartStream_Close(TBlockStream * pStream)1677 static void PartStream_Close(TBlockStream * pStream)
1678 {
1679 PART_FILE_HEADER PartHeader;
1680 ULONGLONG ByteOffset = 0;
1681
1682 if(pStream->FileBitmap && pStream->IsModified)
1683 {
1684 // Prepare the part file header
1685 memset(&PartHeader, 0, sizeof(PART_FILE_HEADER));
1686 PartHeader.PartialVersion = 2;
1687 PartHeader.FileSizeHi = (DWORD)(pStream->StreamSize >> 0x20);
1688 PartHeader.FileSizeLo = (DWORD)(pStream->StreamSize & 0xFFFFFFFF);
1689 PartHeader.BlockSize = pStream->BlockSize;
1690
1691 // Make sure that the header is properly BSWAPed
1692 BSWAP_ARRAY32_UNSIGNED(&PartHeader, sizeof(PART_FILE_HEADER));
1693 sprintf(PartHeader.GameBuildNumber, "%u", (unsigned int)pStream->BuildNumber);
1694
1695 // Write the part header
1696 pStream->BaseWrite(pStream, &ByteOffset, &PartHeader, sizeof(PART_FILE_HEADER));
1697
1698 // Write the block bitmap
1699 BSWAP_ARRAY32_UNSIGNED(pStream->FileBitmap, pStream->BitmapSize);
1700 pStream->BaseWrite(pStream, NULL, pStream->FileBitmap, pStream->BitmapSize);
1701 }
1702
1703 // Close the base class
1704 BlockStream_Close(pStream);
1705 }
1706
PartStream_CreateMirror(TBlockStream * pStream)1707 static bool PartStream_CreateMirror(TBlockStream * pStream)
1708 {
1709 ULONGLONG RemainingSize;
1710 ULONGLONG MasterSize = 0;
1711 ULONGLONG MirrorSize = 0;
1712 LPBYTE FileBitmap = NULL;
1713 DWORD dwBitmapSize;
1714 DWORD dwBlockCount;
1715 bool bNeedCreateMirrorStream = true;
1716 bool bNeedResizeMirrorStream = true;
1717
1718 // Do we have master function and base creation function?
1719 if(pStream->pMaster == NULL || pStream->BaseCreate == NULL)
1720 return false;
1721
1722 // Retrieve the master file size, block count and bitmap size
1723 FileStream_GetSize(pStream->pMaster, &MasterSize);
1724 dwBlockCount = (DWORD)((MasterSize + DEFAULT_BLOCK_SIZE - 1) / DEFAULT_BLOCK_SIZE);
1725 dwBitmapSize = (DWORD)(dwBlockCount * sizeof(PART_FILE_MAP_ENTRY));
1726
1727 // Setup stream size and position
1728 pStream->BuildNumber = DEFAULT_BUILD_NUMBER; // BUGBUG: Really???
1729 pStream->StreamSize = MasterSize;
1730 pStream->StreamPos = 0;
1731
1732 // Open the base stream for write access
1733 if(pStream->BaseOpen(pStream, pStream->szFileName, 0))
1734 {
1735 // If the file open succeeded, check if the file size matches required size
1736 pStream->BaseGetSize(pStream, &MirrorSize);
1737 if(MirrorSize >= sizeof(PART_FILE_HEADER) + dwBitmapSize)
1738 {
1739 // Check if the remaining size is aligned to block
1740 RemainingSize = MirrorSize - sizeof(PART_FILE_HEADER) - dwBitmapSize;
1741 if((RemainingSize & (DEFAULT_BLOCK_SIZE - 1)) == 0 || RemainingSize == MasterSize)
1742 {
1743 // Attempt to load an existing file bitmap
1744 if(PartStream_LoadBitmap(pStream))
1745 return true;
1746 }
1747 }
1748
1749 // We need to create mirror stream
1750 bNeedCreateMirrorStream = false;
1751 }
1752
1753 // Create a new stream, if needed
1754 if(bNeedCreateMirrorStream)
1755 {
1756 if(!pStream->BaseCreate(pStream))
1757 return false;
1758 }
1759
1760 // If we need to, then resize the mirror stream
1761 if(bNeedResizeMirrorStream)
1762 {
1763 if(!pStream->BaseResize(pStream, sizeof(PART_FILE_HEADER) + dwBitmapSize))
1764 return false;
1765 }
1766
1767 // Allocate the bitmap array
1768 FileBitmap = STORM_ALLOC(BYTE, dwBitmapSize);
1769 if(FileBitmap == NULL)
1770 return false;
1771
1772 // Initialize the bitmap
1773 memset(FileBitmap, 0, dwBitmapSize);
1774 pStream->FileBitmap = FileBitmap;
1775 pStream->BitmapSize = dwBitmapSize;
1776 pStream->BlockSize = DEFAULT_BLOCK_SIZE;
1777 pStream->BlockCount = dwBlockCount;
1778 pStream->IsComplete = 0;
1779 pStream->IsModified = 1;
1780
1781 // Note: Don't write the stream bitmap right away.
1782 // Doing so would cause sparse file resize on NTFS,
1783 // which would take long time on larger files.
1784 return true;
1785 }
1786
1787
PartStream_Open(const TCHAR * szFileName,DWORD dwStreamFlags)1788 static TFileStream * PartStream_Open(const TCHAR * szFileName, DWORD dwStreamFlags)
1789 {
1790 TBlockStream * pStream;
1791
1792 // Create new empty stream
1793 pStream = (TBlockStream *)AllocateFileStream(szFileName, sizeof(TBlockStream), dwStreamFlags);
1794 if(pStream == NULL)
1795 return NULL;
1796
1797 // Do we have a master stream?
1798 if(pStream->pMaster != NULL)
1799 {
1800 if(!PartStream_CreateMirror(pStream))
1801 {
1802 FileStream_Close(pStream);
1803 SetLastError(ERROR_FILE_NOT_FOUND);
1804 return NULL;
1805 }
1806 }
1807 else
1808 {
1809 // Attempt to open the base stream
1810 if(!pStream->BaseOpen(pStream, pStream->szFileName, dwStreamFlags))
1811 {
1812 FileStream_Close(pStream);
1813 return NULL;
1814 }
1815
1816 // Load the part stream block map
1817 if(!PartStream_LoadBitmap(pStream))
1818 {
1819 FileStream_Close(pStream);
1820 SetLastError(ERROR_BAD_FORMAT);
1821 return NULL;
1822 }
1823 }
1824
1825 // Set the stream position to zero. Stream size is already set
1826 assert(pStream->StreamSize != 0);
1827 pStream->StreamPos = 0;
1828 pStream->dwFlags |= STREAM_FLAG_READ_ONLY;
1829
1830 // Set new function pointers
1831 pStream->StreamRead = (STREAM_READ)BlockStream_Read;
1832 pStream->StreamGetPos = BlockStream_GetPos;
1833 pStream->StreamGetSize = BlockStream_GetSize;
1834 pStream->StreamClose = (STREAM_CLOSE)PartStream_Close;
1835
1836 // Supply the block functions
1837 pStream->BlockCheck = (BLOCK_CHECK)PartStream_BlockCheck;
1838 pStream->BlockRead = (BLOCK_READ)PartStream_BlockRead;
1839 return pStream;
1840 }
1841
1842 //-----------------------------------------------------------------------------
1843 // Local functions - MPQE stream support
1844
1845 static const char * szKeyTemplate = "expand 32-byte k000000000000000000000000000000000000000000000000";
1846
1847 static const char * AuthCodeArray[] =
1848 {
1849 // Starcraft II (Heart of the Swarm)
1850 // Authentication code URL: http://dist.blizzard.com/mediakey/hots-authenticationcode-bgdl.txt
1851 // -0C- -1C--08- -18--04- -14--00- -10-
1852 "S48B6CDTN5XEQAKQDJNDLJBJ73FDFM3U", // SC2 Heart of the Swarm-all : "expand 32-byte kQAKQ0000FM3UN5XE000073FD6CDT0000LJBJS48B0000DJND"
1853
1854 // Diablo III: Agent.exe (1.0.0.954)
1855 // Address of decryption routine: 00502b00
1856 // Pointer to decryptor object: ECX
1857 // Pointer to key: ECX+0x5C
1858 // Authentication code URL: http://dist.blizzard.com/mediakey/d3-authenticationcode-enGB.txt
1859 // -0C- -1C--08- -18--04- -14--00- -10-
1860 "UCMXF6EJY352EFH4XFRXCFH2XC9MQRZK", // Diablo III Installer (deDE): "expand 32-byte kEFH40000QRZKY3520000XC9MF6EJ0000CFH2UCMX0000XFRX"
1861 "MMKVHY48RP7WXP4GHYBQ7SL9J9UNPHBP", // Diablo III Installer (enGB): "expand 32-byte kXP4G0000PHBPRP7W0000J9UNHY4800007SL9MMKV0000HYBQ"
1862 "8MXLWHQ7VGGLTZ9MQZQSFDCLJYET3CPP", // Diablo III Installer (enSG): "expand 32-byte kTZ9M00003CPPVGGL0000JYETWHQ70000FDCL8MXL0000QZQS"
1863 "EJ2R5TM6XFE2GUNG5QDGHKQ9UAKPWZSZ", // Diablo III Installer (enUS): "expand 32-byte kGUNG0000WZSZXFE20000UAKP5TM60000HKQ9EJ2R00005QDG"
1864 "PBGFBE42Z6LNK65UGJQ3WZVMCLP4HQQT", // Diablo III Installer (esES): "expand 32-byte kK65U0000HQQTZ6LN0000CLP4BE420000WZVMPBGF0000GJQ3"
1865 "X7SEJJS9TSGCW5P28EBSC47AJPEY8VU2", // Diablo III Installer (esMX): "expand 32-byte kW5P200008VU2TSGC0000JPEYJJS90000C47AX7SE00008EBS"
1866 "5KVBQA8VYE6XRY3DLGC5ZDE4XS4P7YA2", // Diablo III Installer (frFR): "expand 32-byte kRY3D00007YA2YE6X0000XS4PQA8V0000ZDE45KVB0000LGC5"
1867 "478JD2K56EVNVVY4XX8TDWYT5B8KB254", // Diablo III Installer (itIT): "expand 32-byte kVVY40000B2546EVN00005B8KD2K50000DWYT478J0000XX8T"
1868 "8TS4VNFQRZTN6YWHE9CHVDH9NVWD474A", // Diablo III Installer (koKR): "expand 32-byte k6YWH0000474ARZTN0000NVWDVNFQ0000VDH98TS40000E9CH"
1869 "LJ52Z32DF4LZ4ZJJXVKK3AZQA6GABLJB", // Diablo III Installer (plPL): "expand 32-byte k4ZJJ0000BLJBF4LZ0000A6GAZ32D00003AZQLJ520000XVKK"
1870 "K6BDHY2ECUE2545YKNLBJPVYWHE7XYAG", // Diablo III Installer (ptBR): "expand 32-byte k545Y0000XYAGCUE20000WHE7HY2E0000JPVYK6BD0000KNLB"
1871 "NDVW8GWLAYCRPGRNY8RT7ZZUQU63VLPR", // Diablo III Installer (ruRU): "expand 32-byte kXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
1872 "6VWCQTN8V3ZZMRUCZXV8A8CGUX2TAA8H", // Diablo III Installer (zhTW): "expand 32-byte kMRUC0000AA8HV3ZZ0000UX2TQTN80000A8CG6VWC0000ZXV8"
1873 // "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", // Diablo III Installer (zhCN): "expand 32-byte kXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
1874
1875 // Starcraft II (Wings of Liberty): Installer.exe (4.1.1.4219)
1876 // Address of decryption routine: 0053A3D0
1877 // Pointer to decryptor object: ECX
1878 // Pointer to key: ECX+0x5C
1879 // Authentication code URL: http://dist.blizzard.com/mediakey/sc2-authenticationcode-enUS.txt
1880 // -0C- -1C--08- -18--04- -14--00- -10-
1881 "Y45MD3CAK4KXSSXHYD9VY64Z8EKJ4XFX", // SC2 Wings of Liberty (deDE): "expand 32-byte kSSXH00004XFXK4KX00008EKJD3CA0000Y64ZY45M0000YD9V"
1882 "G8MN8UDG6NA2ANGY6A3DNY82HRGF29ZH", // SC2 Wings of Liberty (enGB): "expand 32-byte kANGY000029ZH6NA20000HRGF8UDG0000NY82G8MN00006A3D"
1883 "W9RRHLB2FDU9WW5B3ECEBLRSFWZSF7HW", // SC2 Wings of Liberty (enSG): "expand 32-byte kWW5B0000F7HWFDU90000FWZSHLB20000BLRSW9RR00003ECE"
1884 "3DH5RE5NVM5GTFD85LXGWT6FK859ETR5", // SC2 Wings of Liberty (enUS): "expand 32-byte kTFD80000ETR5VM5G0000K859RE5N0000WT6F3DH500005LXG"
1885 "8WLKUAXE94PFQU4Y249PAZ24N4R4XKTQ", // SC2 Wings of Liberty (esES): "expand 32-byte kQU4Y0000XKTQ94PF0000N4R4UAXE0000AZ248WLK0000249P"
1886 "A34DXX3VHGGXSQBRFE5UFFDXMF9G4G54", // SC2 Wings of Liberty (esMX): "expand 32-byte kSQBR00004G54HGGX0000MF9GXX3V0000FFDXA34D0000FE5U"
1887 "ZG7J9K938HJEFWPQUA768MA2PFER6EAJ", // SC2 Wings of Liberty (frFR): "expand 32-byte kFWPQ00006EAJ8HJE0000PFER9K9300008MA2ZG7J0000UA76"
1888 "NE7CUNNNTVAPXV7E3G2BSVBWGVMW8BL2", // SC2 Wings of Liberty (itIT): "expand 32-byte kXV7E00008BL2TVAP0000GVMWUNNN0000SVBWNE7C00003G2B"
1889 "3V9E2FTMBM9QQWK7U6MAMWAZWQDB838F", // SC2 Wings of Liberty (koKR): "expand 32-byte kQWK70000838FBM9Q0000WQDB2FTM0000MWAZ3V9E0000U6MA"
1890 "2NSFB8MELULJ83U6YHA3UP6K4MQD48L6", // SC2 Wings of Liberty (plPL): "expand 32-byte k83U6000048L6LULJ00004MQDB8ME0000UP6K2NSF0000YHA3"
1891 "QA2TZ9EWZ4CUU8BMB5WXCTY65F9CSW4E", // SC2 Wings of Liberty (ptBR): "expand 32-byte kU8BM0000SW4EZ4CU00005F9CZ9EW0000CTY6QA2T0000B5WX"
1892 "VHB378W64BAT9SH7D68VV9NLQDK9YEGT", // SC2 Wings of Liberty (ruRU): "expand 32-byte k9SH70000YEGT4BAT0000QDK978W60000V9NLVHB30000D68V"
1893 "U3NFQJV4M6GC7KBN9XQJ3BRDN3PLD9NE", // SC2 Wings of Liberty (zhTW): "expand 32-byte k7KBN0000D9NEM6GC0000N3PLQJV400003BRDU3NF00009XQJ"
1894
1895 NULL
1896 };
1897
Rol32(DWORD dwValue,DWORD dwRolCount)1898 static DWORD Rol32(DWORD dwValue, DWORD dwRolCount)
1899 {
1900 DWORD dwShiftRight = 32 - dwRolCount;
1901
1902 return (dwValue << dwRolCount) | (dwValue >> dwShiftRight);
1903 }
1904
CreateKeyFromAuthCode(LPBYTE pbKeyBuffer,const char * szAuthCode)1905 static void CreateKeyFromAuthCode(
1906 LPBYTE pbKeyBuffer,
1907 const char * szAuthCode)
1908 {
1909 LPDWORD KeyPosition = (LPDWORD)(pbKeyBuffer + 0x10);
1910 LPDWORD AuthCode32 = (LPDWORD)szAuthCode;
1911
1912 memcpy(pbKeyBuffer, szKeyTemplate, MPQE_CHUNK_SIZE);
1913 KeyPosition[0x00] = AuthCode32[0x03];
1914 KeyPosition[0x02] = AuthCode32[0x07];
1915 KeyPosition[0x03] = AuthCode32[0x02];
1916 KeyPosition[0x05] = AuthCode32[0x06];
1917 KeyPosition[0x06] = AuthCode32[0x01];
1918 KeyPosition[0x08] = AuthCode32[0x05];
1919 KeyPosition[0x09] = AuthCode32[0x00];
1920 KeyPosition[0x0B] = AuthCode32[0x04];
1921 BSWAP_ARRAY32_UNSIGNED(pbKeyBuffer, MPQE_CHUNK_SIZE);
1922 }
1923
DecryptFileChunk(DWORD * MpqData,LPBYTE pbKey,ULONGLONG ByteOffset,DWORD dwLength)1924 static void DecryptFileChunk(
1925 DWORD * MpqData,
1926 LPBYTE pbKey,
1927 ULONGLONG ByteOffset,
1928 DWORD dwLength)
1929 {
1930 ULONGLONG ChunkOffset;
1931 DWORD KeyShuffled[0x10];
1932 DWORD KeyMirror[0x10];
1933 DWORD RoundCount = 0x14;
1934
1935 // Prepare the key
1936 ChunkOffset = ByteOffset / MPQE_CHUNK_SIZE;
1937 memcpy(KeyMirror, pbKey, MPQE_CHUNK_SIZE);
1938 BSWAP_ARRAY32_UNSIGNED(KeyMirror, MPQE_CHUNK_SIZE);
1939 KeyMirror[0x05] = (DWORD)(ChunkOffset >> 32);
1940 KeyMirror[0x08] = (DWORD)(ChunkOffset);
1941
1942 while(dwLength >= MPQE_CHUNK_SIZE)
1943 {
1944 // Shuffle the key - part 1
1945 KeyShuffled[0x0E] = KeyMirror[0x00];
1946 KeyShuffled[0x0C] = KeyMirror[0x01];
1947 KeyShuffled[0x05] = KeyMirror[0x02];
1948 KeyShuffled[0x0F] = KeyMirror[0x03];
1949 KeyShuffled[0x0A] = KeyMirror[0x04];
1950 KeyShuffled[0x07] = KeyMirror[0x05];
1951 KeyShuffled[0x0B] = KeyMirror[0x06];
1952 KeyShuffled[0x09] = KeyMirror[0x07];
1953 KeyShuffled[0x03] = KeyMirror[0x08];
1954 KeyShuffled[0x06] = KeyMirror[0x09];
1955 KeyShuffled[0x08] = KeyMirror[0x0A];
1956 KeyShuffled[0x0D] = KeyMirror[0x0B];
1957 KeyShuffled[0x02] = KeyMirror[0x0C];
1958 KeyShuffled[0x04] = KeyMirror[0x0D];
1959 KeyShuffled[0x01] = KeyMirror[0x0E];
1960 KeyShuffled[0x00] = KeyMirror[0x0F];
1961
1962 // Shuffle the key - part 2
1963 for(DWORD i = 0; i < RoundCount; i += 2)
1964 {
1965 KeyShuffled[0x0A] = KeyShuffled[0x0A] ^ Rol32((KeyShuffled[0x0E] + KeyShuffled[0x02]), 0x07);
1966 KeyShuffled[0x03] = KeyShuffled[0x03] ^ Rol32((KeyShuffled[0x0A] + KeyShuffled[0x0E]), 0x09);
1967 KeyShuffled[0x02] = KeyShuffled[0x02] ^ Rol32((KeyShuffled[0x03] + KeyShuffled[0x0A]), 0x0D);
1968 KeyShuffled[0x0E] = KeyShuffled[0x0E] ^ Rol32((KeyShuffled[0x02] + KeyShuffled[0x03]), 0x12);
1969
1970 KeyShuffled[0x07] = KeyShuffled[0x07] ^ Rol32((KeyShuffled[0x0C] + KeyShuffled[0x04]), 0x07);
1971 KeyShuffled[0x06] = KeyShuffled[0x06] ^ Rol32((KeyShuffled[0x07] + KeyShuffled[0x0C]), 0x09);
1972 KeyShuffled[0x04] = KeyShuffled[0x04] ^ Rol32((KeyShuffled[0x06] + KeyShuffled[0x07]), 0x0D);
1973 KeyShuffled[0x0C] = KeyShuffled[0x0C] ^ Rol32((KeyShuffled[0x04] + KeyShuffled[0x06]), 0x12);
1974
1975 KeyShuffled[0x0B] = KeyShuffled[0x0B] ^ Rol32((KeyShuffled[0x05] + KeyShuffled[0x01]), 0x07);
1976 KeyShuffled[0x08] = KeyShuffled[0x08] ^ Rol32((KeyShuffled[0x0B] + KeyShuffled[0x05]), 0x09);
1977 KeyShuffled[0x01] = KeyShuffled[0x01] ^ Rol32((KeyShuffled[0x08] + KeyShuffled[0x0B]), 0x0D);
1978 KeyShuffled[0x05] = KeyShuffled[0x05] ^ Rol32((KeyShuffled[0x01] + KeyShuffled[0x08]), 0x12);
1979
1980 KeyShuffled[0x09] = KeyShuffled[0x09] ^ Rol32((KeyShuffled[0x0F] + KeyShuffled[0x00]), 0x07);
1981 KeyShuffled[0x0D] = KeyShuffled[0x0D] ^ Rol32((KeyShuffled[0x09] + KeyShuffled[0x0F]), 0x09);
1982 KeyShuffled[0x00] = KeyShuffled[0x00] ^ Rol32((KeyShuffled[0x0D] + KeyShuffled[0x09]), 0x0D);
1983 KeyShuffled[0x0F] = KeyShuffled[0x0F] ^ Rol32((KeyShuffled[0x00] + KeyShuffled[0x0D]), 0x12);
1984
1985 KeyShuffled[0x04] = KeyShuffled[0x04] ^ Rol32((KeyShuffled[0x0E] + KeyShuffled[0x09]), 0x07);
1986 KeyShuffled[0x08] = KeyShuffled[0x08] ^ Rol32((KeyShuffled[0x04] + KeyShuffled[0x0E]), 0x09);
1987 KeyShuffled[0x09] = KeyShuffled[0x09] ^ Rol32((KeyShuffled[0x08] + KeyShuffled[0x04]), 0x0D);
1988 KeyShuffled[0x0E] = KeyShuffled[0x0E] ^ Rol32((KeyShuffled[0x09] + KeyShuffled[0x08]), 0x12);
1989
1990 KeyShuffled[0x01] = KeyShuffled[0x01] ^ Rol32((KeyShuffled[0x0C] + KeyShuffled[0x0A]), 0x07);
1991 KeyShuffled[0x0D] = KeyShuffled[0x0D] ^ Rol32((KeyShuffled[0x01] + KeyShuffled[0x0C]), 0x09);
1992 KeyShuffled[0x0A] = KeyShuffled[0x0A] ^ Rol32((KeyShuffled[0x0D] + KeyShuffled[0x01]), 0x0D);
1993 KeyShuffled[0x0C] = KeyShuffled[0x0C] ^ Rol32((KeyShuffled[0x0A] + KeyShuffled[0x0D]), 0x12);
1994
1995 KeyShuffled[0x00] = KeyShuffled[0x00] ^ Rol32((KeyShuffled[0x05] + KeyShuffled[0x07]), 0x07);
1996 KeyShuffled[0x03] = KeyShuffled[0x03] ^ Rol32((KeyShuffled[0x00] + KeyShuffled[0x05]), 0x09);
1997 KeyShuffled[0x07] = KeyShuffled[0x07] ^ Rol32((KeyShuffled[0x03] + KeyShuffled[0x00]), 0x0D);
1998 KeyShuffled[0x05] = KeyShuffled[0x05] ^ Rol32((KeyShuffled[0x07] + KeyShuffled[0x03]), 0x12);
1999
2000 KeyShuffled[0x02] = KeyShuffled[0x02] ^ Rol32((KeyShuffled[0x0F] + KeyShuffled[0x0B]), 0x07);
2001 KeyShuffled[0x06] = KeyShuffled[0x06] ^ Rol32((KeyShuffled[0x02] + KeyShuffled[0x0F]), 0x09);
2002 KeyShuffled[0x0B] = KeyShuffled[0x0B] ^ Rol32((KeyShuffled[0x06] + KeyShuffled[0x02]), 0x0D);
2003 KeyShuffled[0x0F] = KeyShuffled[0x0F] ^ Rol32((KeyShuffled[0x0B] + KeyShuffled[0x06]), 0x12);
2004 }
2005
2006 // Decrypt one data chunk
2007 BSWAP_ARRAY32_UNSIGNED(MpqData, MPQE_CHUNK_SIZE);
2008 MpqData[0x00] = MpqData[0x00] ^ (KeyShuffled[0x0E] + KeyMirror[0x00]);
2009 MpqData[0x01] = MpqData[0x01] ^ (KeyShuffled[0x04] + KeyMirror[0x0D]);
2010 MpqData[0x02] = MpqData[0x02] ^ (KeyShuffled[0x08] + KeyMirror[0x0A]);
2011 MpqData[0x03] = MpqData[0x03] ^ (KeyShuffled[0x09] + KeyMirror[0x07]);
2012 MpqData[0x04] = MpqData[0x04] ^ (KeyShuffled[0x0A] + KeyMirror[0x04]);
2013 MpqData[0x05] = MpqData[0x05] ^ (KeyShuffled[0x0C] + KeyMirror[0x01]);
2014 MpqData[0x06] = MpqData[0x06] ^ (KeyShuffled[0x01] + KeyMirror[0x0E]);
2015 MpqData[0x07] = MpqData[0x07] ^ (KeyShuffled[0x0D] + KeyMirror[0x0B]);
2016 MpqData[0x08] = MpqData[0x08] ^ (KeyShuffled[0x03] + KeyMirror[0x08]);
2017 MpqData[0x09] = MpqData[0x09] ^ (KeyShuffled[0x07] + KeyMirror[0x05]);
2018 MpqData[0x0A] = MpqData[0x0A] ^ (KeyShuffled[0x05] + KeyMirror[0x02]);
2019 MpqData[0x0B] = MpqData[0x0B] ^ (KeyShuffled[0x00] + KeyMirror[0x0F]);
2020 MpqData[0x0C] = MpqData[0x0C] ^ (KeyShuffled[0x02] + KeyMirror[0x0C]);
2021 MpqData[0x0D] = MpqData[0x0D] ^ (KeyShuffled[0x06] + KeyMirror[0x09]);
2022 MpqData[0x0E] = MpqData[0x0E] ^ (KeyShuffled[0x0B] + KeyMirror[0x06]);
2023 MpqData[0x0F] = MpqData[0x0F] ^ (KeyShuffled[0x0F] + KeyMirror[0x03]);
2024 BSWAP_ARRAY32_UNSIGNED(MpqData, MPQE_CHUNK_SIZE);
2025
2026 // Update byte offset in the key
2027 KeyMirror[0x08]++;
2028 if(KeyMirror[0x08] == 0)
2029 KeyMirror[0x05]++;
2030
2031 // Move pointers and decrease number of bytes to decrypt
2032 MpqData += (MPQE_CHUNK_SIZE / sizeof(DWORD));
2033 dwLength -= MPQE_CHUNK_SIZE;
2034 }
2035 }
2036
MpqeStream_DetectFileKey(TEncryptedStream * pStream)2037 static bool MpqeStream_DetectFileKey(TEncryptedStream * pStream)
2038 {
2039 ULONGLONG ByteOffset = 0;
2040 BYTE EncryptedHeader[MPQE_CHUNK_SIZE];
2041 BYTE FileHeader[MPQE_CHUNK_SIZE];
2042
2043 // Read the first file chunk
2044 if(pStream->BaseRead(pStream, &ByteOffset, EncryptedHeader, sizeof(EncryptedHeader)))
2045 {
2046 // We just try all known keys one by one
2047 for(int i = 0; AuthCodeArray[i] != NULL; i++)
2048 {
2049 // Prepare they decryption key from game serial number
2050 CreateKeyFromAuthCode(pStream->Key, AuthCodeArray[i]);
2051
2052 // Try to decrypt with the given key
2053 memcpy(FileHeader, EncryptedHeader, MPQE_CHUNK_SIZE);
2054 DecryptFileChunk((LPDWORD)FileHeader, pStream->Key, ByteOffset, MPQE_CHUNK_SIZE);
2055
2056 // We check the decrypted data
2057 // All known encrypted MPQs have header at the begin of the file,
2058 // so we check for MPQ signature there.
2059 if(FileHeader[0] == 'M' && FileHeader[1] == 'P' && FileHeader[2] == 'Q')
2060 {
2061 // Update the stream size
2062 pStream->StreamSize = pStream->Base.File.FileSize;
2063
2064 // Fill the block information
2065 pStream->BlockSize = MPQE_CHUNK_SIZE;
2066 pStream->BlockCount = (DWORD)(pStream->Base.File.FileSize + MPQE_CHUNK_SIZE - 1) / MPQE_CHUNK_SIZE;
2067 pStream->IsComplete = 1;
2068 return true;
2069 }
2070 }
2071 }
2072
2073 // Key not found, sorry
2074 return false;
2075 }
2076
MpqeStream_BlockRead(TEncryptedStream * pStream,ULONGLONG StartOffset,ULONGLONG EndOffset,LPBYTE BlockBuffer,DWORD BytesNeeded,bool bAvailable)2077 static bool MpqeStream_BlockRead(
2078 TEncryptedStream * pStream,
2079 ULONGLONG StartOffset,
2080 ULONGLONG EndOffset,
2081 LPBYTE BlockBuffer,
2082 DWORD BytesNeeded,
2083 bool bAvailable)
2084 {
2085 DWORD dwBytesToRead;
2086
2087 assert((StartOffset & (pStream->BlockSize - 1)) == 0);
2088 assert(StartOffset < EndOffset);
2089 assert(bAvailable != false);
2090 BytesNeeded = BytesNeeded;
2091 bAvailable = bAvailable;
2092
2093 // Read the file from the stream as-is
2094 // Limit the reading to number of blocks really needed
2095 dwBytesToRead = (DWORD)(EndOffset - StartOffset);
2096 if(!pStream->BaseRead(pStream, &StartOffset, BlockBuffer, dwBytesToRead))
2097 return false;
2098
2099 // Decrypt the data
2100 dwBytesToRead = (dwBytesToRead + MPQE_CHUNK_SIZE - 1) & ~(MPQE_CHUNK_SIZE - 1);
2101 DecryptFileChunk((LPDWORD)BlockBuffer, pStream->Key, StartOffset, dwBytesToRead);
2102 return true;
2103 }
2104
MpqeStream_Open(const TCHAR * szFileName,DWORD dwStreamFlags)2105 static TFileStream * MpqeStream_Open(const TCHAR * szFileName, DWORD dwStreamFlags)
2106 {
2107 TEncryptedStream * pStream;
2108
2109 // Create new empty stream
2110 pStream = (TEncryptedStream *)AllocateFileStream(szFileName, sizeof(TEncryptedStream), dwStreamFlags);
2111 if(pStream == NULL)
2112 return NULL;
2113
2114 // Attempt to open the base stream
2115 assert(pStream->BaseOpen != NULL);
2116 if(!pStream->BaseOpen(pStream, pStream->szFileName, dwStreamFlags))
2117 return NULL;
2118
2119 // Determine the encryption key for the MPQ
2120 if(MpqeStream_DetectFileKey(pStream))
2121 {
2122 // Set the stream position and size
2123 assert(pStream->StreamSize != 0);
2124 pStream->StreamPos = 0;
2125 pStream->dwFlags |= STREAM_FLAG_READ_ONLY;
2126
2127 // Set new function pointers
2128 pStream->StreamRead = (STREAM_READ)BlockStream_Read;
2129 pStream->StreamGetPos = BlockStream_GetPos;
2130 pStream->StreamGetSize = BlockStream_GetSize;
2131 pStream->StreamClose = pStream->BaseClose;
2132
2133 // Supply the block functions
2134 pStream->BlockRead = (BLOCK_READ)MpqeStream_BlockRead;
2135 return pStream;
2136 }
2137
2138 // Cleanup the stream and return
2139 FileStream_Close(pStream);
2140 SetLastError(ERROR_UNKNOWN_FILE_KEY);
2141 return NULL;
2142 }
2143
2144 //-----------------------------------------------------------------------------
2145 // Local functions - Block4 stream support
2146
2147 #define BLOCK4_BLOCK_SIZE 0x4000 // Size of one block
2148 #define BLOCK4_HASH_SIZE 0x20 // Size of MD5 hash that is after each block
2149 #define BLOCK4_MAX_BLOCKS 0x00002000 // Maximum amount of blocks per file
2150 #define BLOCK4_MAX_FSIZE 0x08040000 // Max size of one file
2151
Block4Stream_BlockRead(TBlockStream * pStream,ULONGLONG StartOffset,ULONGLONG EndOffset,LPBYTE BlockBuffer,DWORD BytesNeeded,bool bAvailable)2152 static bool Block4Stream_BlockRead(
2153 TBlockStream * pStream, // Pointer to an open stream
2154 ULONGLONG StartOffset,
2155 ULONGLONG EndOffset,
2156 LPBYTE BlockBuffer,
2157 DWORD BytesNeeded,
2158 bool bAvailable)
2159 {
2160 TBaseProviderData * BaseArray = (TBaseProviderData *)pStream->FileBitmap;
2161 ULONGLONG ByteOffset;
2162 DWORD BytesToRead;
2163 DWORD StreamIndex;
2164 DWORD BlockIndex;
2165 bool bResult;
2166
2167 // The starting offset must be aligned to size of the block
2168 assert(pStream->FileBitmap != NULL);
2169 assert((StartOffset & (pStream->BlockSize - 1)) == 0);
2170 assert(StartOffset < EndOffset);
2171 assert(bAvailable == true);
2172
2173 // Keep compiler happy
2174 bAvailable = bAvailable;
2175 EndOffset = EndOffset;
2176
2177 while(BytesNeeded != 0)
2178 {
2179 // Calculate the block index and the file index
2180 StreamIndex = (DWORD)((StartOffset / pStream->BlockSize) / BLOCK4_MAX_BLOCKS);
2181 BlockIndex = (DWORD)((StartOffset / pStream->BlockSize) % BLOCK4_MAX_BLOCKS);
2182 if(StreamIndex > pStream->BitmapSize)
2183 return false;
2184
2185 // Calculate the block offset
2186 ByteOffset = ((ULONGLONG)BlockIndex * (BLOCK4_BLOCK_SIZE + BLOCK4_HASH_SIZE));
2187 BytesToRead = STORMLIB_MIN(BytesNeeded, BLOCK4_BLOCK_SIZE);
2188
2189 // Read from the base stream
2190 pStream->Base = BaseArray[StreamIndex];
2191 bResult = pStream->BaseRead(pStream, &ByteOffset, BlockBuffer, BytesToRead);
2192 BaseArray[StreamIndex] = pStream->Base;
2193
2194 // Did the result succeed?
2195 if(bResult == false)
2196 return false;
2197
2198 // Move pointers
2199 StartOffset += BytesToRead;
2200 BlockBuffer += BytesToRead;
2201 BytesNeeded -= BytesToRead;
2202 }
2203
2204 return true;
2205 }
2206
2207
Block4Stream_Close(TBlockStream * pStream)2208 static void Block4Stream_Close(TBlockStream * pStream)
2209 {
2210 TBaseProviderData * BaseArray = (TBaseProviderData *)pStream->FileBitmap;
2211
2212 // If we have a non-zero count of base streams,
2213 // we have to close them all
2214 if(BaseArray != NULL)
2215 {
2216 // Close all base streams
2217 for(DWORD i = 0; i < pStream->BitmapSize; i++)
2218 {
2219 memcpy(&pStream->Base, BaseArray + i, sizeof(TBaseProviderData));
2220 pStream->BaseClose(pStream);
2221 }
2222 }
2223
2224 // Free the data map, if any
2225 if(pStream->FileBitmap != NULL)
2226 STORM_FREE(pStream->FileBitmap);
2227 pStream->FileBitmap = NULL;
2228
2229 // Do not call the BaseClose function,
2230 // we closed all handles already
2231 return;
2232 }
2233
Block4Stream_Open(const TCHAR * szFileName,DWORD dwStreamFlags)2234 static TFileStream * Block4Stream_Open(const TCHAR * szFileName, DWORD dwStreamFlags)
2235 {
2236 TBaseProviderData * NewBaseArray = NULL;
2237 ULONGLONG RemainderBlock;
2238 ULONGLONG BlockCount;
2239 ULONGLONG FileSize;
2240 TBlockStream * pStream;
2241 TCHAR * szNameBuff;
2242 size_t nNameLength;
2243 DWORD dwBaseFiles = 0;
2244 DWORD dwBaseFlags;
2245
2246 // Create new empty stream
2247 pStream = (TBlockStream *)AllocateFileStream(szFileName, sizeof(TBlockStream), dwStreamFlags);
2248 if(pStream == NULL)
2249 return NULL;
2250
2251 // Sanity check
2252 assert(pStream->BaseOpen != NULL);
2253
2254 // Get the length of the file name without numeric suffix
2255 nNameLength = _tcslen(pStream->szFileName);
2256 if(pStream->szFileName[nNameLength - 2] == '.' && pStream->szFileName[nNameLength - 1] == '0')
2257 nNameLength -= 2;
2258 pStream->szFileName[nNameLength] = 0;
2259
2260 // Supply the stream functions
2261 pStream->StreamRead = (STREAM_READ)BlockStream_Read;
2262 pStream->StreamGetSize = BlockStream_GetSize;
2263 pStream->StreamGetPos = BlockStream_GetPos;
2264 pStream->StreamClose = (STREAM_CLOSE)Block4Stream_Close;
2265 pStream->BlockRead = (BLOCK_READ)Block4Stream_BlockRead;
2266
2267 // Allocate work space for numeric names
2268 szNameBuff = STORM_ALLOC(TCHAR, nNameLength + 4);
2269 if(szNameBuff != NULL)
2270 {
2271 // Set the base flags
2272 dwBaseFlags = (dwStreamFlags & STREAM_PROVIDERS_MASK) | STREAM_FLAG_READ_ONLY;
2273
2274 // Go all suffixes from 0 to 30
2275 for(int nSuffix = 0; nSuffix < 30; nSuffix++)
2276 {
2277 // Open the n-th file
2278 _stprintf(szNameBuff, _T("%s.%u"), pStream->szFileName, nSuffix);
2279 if(!pStream->BaseOpen(pStream, szNameBuff, dwBaseFlags))
2280 break;
2281
2282 // If the open succeeded, we re-allocate the base provider array
2283 NewBaseArray = STORM_ALLOC(TBaseProviderData, dwBaseFiles + 1);
2284 if(NewBaseArray == NULL)
2285 {
2286 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
2287 return NULL;
2288 }
2289
2290 // Copy the old base data array to the new base data array
2291 if(pStream->FileBitmap != NULL)
2292 {
2293 memcpy(NewBaseArray, pStream->FileBitmap, sizeof(TBaseProviderData) * dwBaseFiles);
2294 STORM_FREE(pStream->FileBitmap);
2295 }
2296
2297 // Also copy the opened base array
2298 memcpy(NewBaseArray + dwBaseFiles, &pStream->Base, sizeof(TBaseProviderData));
2299 pStream->FileBitmap = NewBaseArray;
2300 dwBaseFiles++;
2301
2302 // Get the size of the base stream
2303 pStream->BaseGetSize(pStream, &FileSize);
2304 assert(FileSize <= BLOCK4_MAX_FSIZE);
2305 RemainderBlock = FileSize % (BLOCK4_BLOCK_SIZE + BLOCK4_HASH_SIZE);
2306 BlockCount = FileSize / (BLOCK4_BLOCK_SIZE + BLOCK4_HASH_SIZE);
2307
2308 // Increment the stream size and number of blocks
2309 pStream->StreamSize += (BlockCount * BLOCK4_BLOCK_SIZE);
2310 pStream->BlockCount += (DWORD)BlockCount;
2311
2312 // Is this the last file?
2313 if(FileSize < BLOCK4_MAX_FSIZE)
2314 {
2315 if(RemainderBlock)
2316 {
2317 pStream->StreamSize += (RemainderBlock - BLOCK4_HASH_SIZE);
2318 pStream->BlockCount++;
2319 }
2320 break;
2321 }
2322 }
2323
2324 // Fill the remainining block stream variables
2325 pStream->BitmapSize = dwBaseFiles;
2326 pStream->BlockSize = BLOCK4_BLOCK_SIZE;
2327 pStream->IsComplete = 1;
2328 pStream->IsModified = 0;
2329
2330 // Fill the remaining stream variables
2331 pStream->StreamPos = 0;
2332 pStream->dwFlags |= STREAM_FLAG_READ_ONLY;
2333
2334 STORM_FREE(szNameBuff);
2335 }
2336
2337 // If we opened something, return success
2338 if(dwBaseFiles == 0)
2339 {
2340 FileStream_Close(pStream);
2341 SetLastError(ERROR_FILE_NOT_FOUND);
2342 pStream = NULL;
2343 }
2344
2345 return pStream;
2346 }
2347
2348 //-----------------------------------------------------------------------------
2349 // Public functions
2350
2351 /**
2352 * This function creates a new file for read-write access
2353 *
2354 * - If the current platform supports file sharing,
2355 * the file must be created for read sharing (i.e. another application
2356 * can open the file for read, but not for write)
2357 * - If the file does not exist, the function must create new one
2358 * - If the file exists, the function must rewrite it and set to zero size
2359 * - The parameters of the function must be validate by the caller
2360 * - The function must initialize all stream function pointers in TFileStream
2361 * - If the function fails from any reason, it must close all handles
2362 * and free all memory that has been allocated in the process of stream creation,
2363 * including the TFileStream structure itself
2364 *
2365 * \a szFileName Name of the file to create
2366 */
2367
FileStream_CreateFile(const TCHAR * szFileName,DWORD dwStreamFlags)2368 TFileStream * FileStream_CreateFile(
2369 const TCHAR * szFileName,
2370 DWORD dwStreamFlags)
2371 {
2372 TFileStream * pStream;
2373
2374 // We only support creation of flat, local file
2375 if((dwStreamFlags & (STREAM_PROVIDERS_MASK)) != (STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE))
2376 {
2377 SetLastError(ERROR_NOT_SUPPORTED);
2378 return NULL;
2379 }
2380
2381 // Allocate file stream structure for flat stream
2382 pStream = AllocateFileStream(szFileName, sizeof(TBlockStream), dwStreamFlags);
2383 if(pStream != NULL)
2384 {
2385 // Attempt to create the disk file
2386 if(BaseFile_Create(pStream))
2387 {
2388 // Fill the stream provider functions
2389 pStream->StreamRead = pStream->BaseRead;
2390 pStream->StreamWrite = pStream->BaseWrite;
2391 pStream->StreamResize = pStream->BaseResize;
2392 pStream->StreamGetSize = pStream->BaseGetSize;
2393 pStream->StreamGetPos = pStream->BaseGetPos;
2394 pStream->StreamClose = pStream->BaseClose;
2395 return pStream;
2396 }
2397
2398 // File create failed, delete the stream
2399 STORM_FREE(pStream);
2400 pStream = NULL;
2401 }
2402
2403 // Return the stream
2404 return pStream;
2405 }
2406
2407 /**
2408 * This function opens an existing file for read or read-write access
2409 * - If the current platform supports file sharing,
2410 * the file must be open for read sharing (i.e. another application
2411 * can open the file for read, but not for write)
2412 * - If the file does not exist, the function must return NULL
2413 * - If the file exists but cannot be open, then function must return NULL
2414 * - The parameters of the function must be validate by the caller
2415 * - The function must initialize all stream function pointers in TFileStream
2416 * - If the function fails from any reason, it must close all handles
2417 * and free all memory that has been allocated in the process of stream creation,
2418 * including the TFileStream structure itself
2419 *
2420 * \a szFileName Name of the file to open
2421 * \a dwStreamFlags specifies the provider and base storage type
2422 */
2423
FileStream_OpenFile(const TCHAR * szFileName,DWORD dwStreamFlags)2424 TFileStream * FileStream_OpenFile(
2425 const TCHAR * szFileName,
2426 DWORD dwStreamFlags)
2427 {
2428 DWORD dwProvider = dwStreamFlags & STREAM_PROVIDERS_MASK;
2429 size_t nPrefixLength = FileStream_Prefix(szFileName, &dwProvider);
2430
2431 // Re-assemble the stream flags
2432 dwStreamFlags = (dwStreamFlags & STREAM_OPTIONS_MASK) | dwProvider;
2433 szFileName += nPrefixLength;
2434
2435 // Perform provider-specific open
2436 switch(dwStreamFlags & STREAM_PROVIDER_MASK)
2437 {
2438 case STREAM_PROVIDER_FLAT:
2439 return FlatStream_Open(szFileName, dwStreamFlags);
2440
2441 case STREAM_PROVIDER_PARTIAL:
2442 return PartStream_Open(szFileName, dwStreamFlags);
2443
2444 case STREAM_PROVIDER_MPQE:
2445 return MpqeStream_Open(szFileName, dwStreamFlags);
2446
2447 case STREAM_PROVIDER_BLOCK4:
2448 return Block4Stream_Open(szFileName, dwStreamFlags);
2449
2450 default:
2451 SetLastError(ERROR_INVALID_PARAMETER);
2452 return NULL;
2453 }
2454 }
2455
2456 /**
2457 * Returns the file name of the stream
2458 *
2459 * \a pStream Pointer to an open stream
2460 */
FileStream_GetFileName(TFileStream * pStream)2461 const TCHAR * FileStream_GetFileName(TFileStream * pStream)
2462 {
2463 assert(pStream != NULL);
2464 return pStream->szFileName;
2465 }
2466
2467 /**
2468 * Returns the length of the provider prefix. Returns zero if no prefix
2469 *
2470 * \a szFileName Pointer to a stream name (file, mapped file, URL)
2471 * \a pdwStreamProvider Pointer to a DWORD variable that receives stream provider (STREAM_PROVIDER_XXX)
2472 */
2473
FileStream_Prefix(const TCHAR * szFileName,DWORD * pdwProvider)2474 size_t FileStream_Prefix(const TCHAR * szFileName, DWORD * pdwProvider)
2475 {
2476 size_t nPrefixLength1 = 0;
2477 size_t nPrefixLength2 = 0;
2478 DWORD dwProvider = 0;
2479
2480 if(szFileName != NULL)
2481 {
2482 //
2483 // Determine the stream provider
2484 //
2485
2486 if(!_tcsnicmp(szFileName, _T("flat-"), 5))
2487 {
2488 dwProvider |= STREAM_PROVIDER_FLAT;
2489 nPrefixLength1 = 5;
2490 }
2491
2492 else if(!_tcsnicmp(szFileName, _T("part-"), 5))
2493 {
2494 dwProvider |= STREAM_PROVIDER_PARTIAL;
2495 nPrefixLength1 = 5;
2496 }
2497
2498 else if(!_tcsnicmp(szFileName, _T("mpqe-"), 5))
2499 {
2500 dwProvider |= STREAM_PROVIDER_MPQE;
2501 nPrefixLength1 = 5;
2502 }
2503
2504 else if(!_tcsnicmp(szFileName, _T("blk4-"), 5))
2505 {
2506 dwProvider |= STREAM_PROVIDER_BLOCK4;
2507 nPrefixLength1 = 5;
2508 }
2509
2510 //
2511 // Determine the base provider
2512 //
2513
2514 if(!_tcsnicmp(szFileName+nPrefixLength1, _T("file:"), 5))
2515 {
2516 dwProvider |= BASE_PROVIDER_FILE;
2517 nPrefixLength2 = 5;
2518 }
2519
2520 else if(!_tcsnicmp(szFileName+nPrefixLength1, _T("map:"), 4))
2521 {
2522 dwProvider |= BASE_PROVIDER_MAP;
2523 nPrefixLength2 = 4;
2524 }
2525
2526 else if(!_tcsnicmp(szFileName+nPrefixLength1, _T("http:"), 5))
2527 {
2528 dwProvider |= BASE_PROVIDER_HTTP;
2529 nPrefixLength2 = 5;
2530 }
2531
2532 // Only accept stream provider if we recognized the base provider
2533 if(nPrefixLength2 != 0)
2534 {
2535 // It is also allowed to put "//" after the base provider, e.g. "file://", "http://"
2536 if(szFileName[nPrefixLength1+nPrefixLength2] == '/' && szFileName[nPrefixLength1+nPrefixLength2+1] == '/')
2537 nPrefixLength2 += 2;
2538
2539 if(pdwProvider != NULL)
2540 *pdwProvider = dwProvider;
2541 return nPrefixLength1 + nPrefixLength2;
2542 }
2543 }
2544
2545 return 0;
2546 }
2547
2548 /**
2549 * Sets a download callback. Whenever the stream needs to download one or more blocks
2550 * from the server, the callback is called
2551 *
2552 * \a pStream Pointer to an open stream
2553 * \a pfnCallback Pointer to callback function
2554 * \a pvUserData Arbitrary user pointer passed to the download callback
2555 */
2556
FileStream_SetCallback(TFileStream * pStream,SFILE_DOWNLOAD_CALLBACK pfnCallback,void * pvUserData)2557 bool FileStream_SetCallback(TFileStream * pStream, SFILE_DOWNLOAD_CALLBACK pfnCallback, void * pvUserData)
2558 {
2559 TBlockStream * pBlockStream = (TBlockStream *)pStream;
2560
2561 if(pStream->BlockRead == NULL)
2562 {
2563 SetLastError(ERROR_NOT_SUPPORTED);
2564 return false;
2565 }
2566
2567 pBlockStream->pfnCallback = pfnCallback;
2568 pBlockStream->UserData = pvUserData;
2569 return true;
2570 }
2571
2572 /**
2573 * This function gives the block map. The 'pvBitmap' pointer must point to a buffer
2574 * of at least sizeof(STREAM_BLOCK_MAP) size. It can also have size of the complete
2575 * block map (i.e. sizeof(STREAM_BLOCK_MAP) + BitmapSize). In that case, the function
2576 * also copies the bit-based block map.
2577 *
2578 * \a pStream Pointer to an open stream
2579 * \a pvBitmap Pointer to buffer where the block map will be stored
2580 * \a cbBitmap Length of the buffer, of the block map
2581 * \a cbLengthNeeded Length of the bitmap, in bytes
2582 */
2583
FileStream_GetBitmap(TFileStream * pStream,void * pvBitmap,DWORD cbBitmap,DWORD * pcbLengthNeeded)2584 bool FileStream_GetBitmap(TFileStream * pStream, void * pvBitmap, DWORD cbBitmap, DWORD * pcbLengthNeeded)
2585 {
2586 TStreamBitmap * pBitmap = (TStreamBitmap *)pvBitmap;
2587 TBlockStream * pBlockStream = (TBlockStream *)pStream;
2588 ULONGLONG BlockOffset;
2589 LPBYTE Bitmap = (LPBYTE)(pBitmap + 1);
2590 DWORD BitmapSize;
2591 DWORD BlockCount;
2592 DWORD BlockSize;
2593 bool bResult = false;
2594
2595 // Retrieve the size of one block
2596 if(pStream->BlockCheck != NULL)
2597 {
2598 BlockCount = pBlockStream->BlockCount;
2599 BlockSize = pBlockStream->BlockSize;
2600 }
2601 else
2602 {
2603 BlockCount = (DWORD)((pStream->StreamSize + DEFAULT_BLOCK_SIZE - 1) / DEFAULT_BLOCK_SIZE);
2604 BlockSize = DEFAULT_BLOCK_SIZE;
2605 }
2606
2607 // Fill-in the variables
2608 BitmapSize = (BlockCount + 7) / 8;
2609
2610 // Give the number of blocks
2611 if(pcbLengthNeeded != NULL)
2612 pcbLengthNeeded[0] = sizeof(TStreamBitmap) + BitmapSize;
2613
2614 // If the length of the buffer is not enough
2615 if(pvBitmap != NULL && cbBitmap != 0)
2616 {
2617 // Give the STREAM_BLOCK_MAP structure
2618 if(cbBitmap >= sizeof(TStreamBitmap))
2619 {
2620 pBitmap->StreamSize = pStream->StreamSize;
2621 pBitmap->BitmapSize = BitmapSize;
2622 pBitmap->BlockCount = BlockCount;
2623 pBitmap->BlockSize = BlockSize;
2624 pBitmap->IsComplete = (pStream->BlockCheck != NULL) ? pBlockStream->IsComplete : 1;
2625 bResult = true;
2626 }
2627
2628 // Give the block bitmap, if enough space
2629 if(cbBitmap >= sizeof(TStreamBitmap) + BitmapSize)
2630 {
2631 // Version with bitmap present
2632 if(pStream->BlockCheck != NULL)
2633 {
2634 DWORD ByteIndex = 0;
2635 BYTE BitMask = 0x01;
2636
2637 // Initialize the map with zeros
2638 memset(Bitmap, 0, BitmapSize);
2639
2640 // Fill the map
2641 for(BlockOffset = 0; BlockOffset < pStream->StreamSize; BlockOffset += BlockSize)
2642 {
2643 // Set the bit if the block is present
2644 if(pBlockStream->BlockCheck(pStream, BlockOffset))
2645 Bitmap[ByteIndex] |= BitMask;
2646
2647 // Move bit position
2648 ByteIndex += (BitMask >> 0x07);
2649 BitMask = (BitMask >> 0x07) | (BitMask << 0x01);
2650 }
2651 }
2652 else
2653 {
2654 memset(Bitmap, 0xFF, BitmapSize);
2655 }
2656 }
2657 }
2658
2659 // Set last error value and return
2660 if(bResult == false)
2661 SetLastError(ERROR_INSUFFICIENT_BUFFER);
2662 return bResult;
2663 }
2664
2665 /**
2666 * Reads data from the stream
2667 *
2668 * - Returns true if the read operation succeeded and all bytes have been read
2669 * - Returns false if either read failed or not all bytes have been read
2670 * - If the pByteOffset is NULL, the function must read the data from the current file position
2671 * - The function can be called with dwBytesToRead = 0. In that case, pvBuffer is ignored
2672 * and the function just adjusts file pointer.
2673 *
2674 * \a pStream Pointer to an open stream
2675 * \a pByteOffset Pointer to file byte offset. If NULL, it reads from the current position
2676 * \a pvBuffer Pointer to data to be read
2677 * \a dwBytesToRead Number of bytes to read from the file
2678 *
2679 * \returns
2680 * - If the function reads the required amount of bytes, it returns true.
2681 * - If the function reads less than required bytes, it returns false and GetLastError() returns ERROR_HANDLE_EOF
2682 * - If the function fails, it reads false and GetLastError() returns an error code different from ERROR_HANDLE_EOF
2683 */
FileStream_Read(TFileStream * pStream,ULONGLONG * pByteOffset,void * pvBuffer,DWORD dwBytesToRead)2684 bool FileStream_Read(TFileStream * pStream, ULONGLONG * pByteOffset, void * pvBuffer, DWORD dwBytesToRead)
2685 {
2686 assert(pStream->StreamRead != NULL);
2687 return pStream->StreamRead(pStream, pByteOffset, pvBuffer, dwBytesToRead);
2688 }
2689
2690 /**
2691 * This function writes data to the stream
2692 *
2693 * - Returns true if the write operation succeeded and all bytes have been written
2694 * - Returns false if either write failed or not all bytes have been written
2695 * - If the pByteOffset is NULL, the function must write the data to the current file position
2696 *
2697 * \a pStream Pointer to an open stream
2698 * \a pByteOffset Pointer to file byte offset. If NULL, it reads from the current position
2699 * \a pvBuffer Pointer to data to be written
2700 * \a dwBytesToWrite Number of bytes to write to the file
2701 */
FileStream_Write(TFileStream * pStream,ULONGLONG * pByteOffset,const void * pvBuffer,DWORD dwBytesToWrite)2702 bool FileStream_Write(TFileStream * pStream, ULONGLONG * pByteOffset, const void * pvBuffer, DWORD dwBytesToWrite)
2703 {
2704 if(pStream->dwFlags & STREAM_FLAG_READ_ONLY)
2705 {
2706 SetLastError(ERROR_ACCESS_DENIED);
2707 return false;
2708 }
2709
2710 assert(pStream->StreamWrite != NULL);
2711 return pStream->StreamWrite(pStream, pByteOffset, pvBuffer, dwBytesToWrite);
2712 }
2713
2714 /**
2715 * Returns the size of a file
2716 *
2717 * \a pStream Pointer to an open stream
2718 * \a FileSize Pointer where to store the file size
2719 */
FileStream_GetSize(TFileStream * pStream,ULONGLONG * pFileSize)2720 bool FileStream_GetSize(TFileStream * pStream, ULONGLONG * pFileSize)
2721 {
2722 assert(pStream->StreamGetSize != NULL);
2723 return pStream->StreamGetSize(pStream, pFileSize);
2724 }
2725
2726 /**
2727 * Sets the size of a file
2728 *
2729 * \a pStream Pointer to an open stream
2730 * \a NewFileSize File size to set
2731 */
FileStream_SetSize(TFileStream * pStream,ULONGLONG NewFileSize)2732 bool FileStream_SetSize(TFileStream * pStream, ULONGLONG NewFileSize)
2733 {
2734 if(pStream->dwFlags & STREAM_FLAG_READ_ONLY)
2735 {
2736 SetLastError(ERROR_ACCESS_DENIED);
2737 return false;
2738 }
2739
2740 assert(pStream->StreamResize != NULL);
2741 return pStream->StreamResize(pStream, NewFileSize);
2742 }
2743
2744 /**
2745 * This function returns the current file position
2746 * \a pStream
2747 * \a pByteOffset
2748 */
FileStream_GetPos(TFileStream * pStream,ULONGLONG * pByteOffset)2749 bool FileStream_GetPos(TFileStream * pStream, ULONGLONG * pByteOffset)
2750 {
2751 assert(pStream->StreamGetPos != NULL);
2752 return pStream->StreamGetPos(pStream, pByteOffset);
2753 }
2754
2755 /**
2756 * Returns the last write time of a file
2757 *
2758 * \a pStream Pointer to an open stream
2759 * \a pFileType Pointer where to store the file last write time
2760 */
FileStream_GetTime(TFileStream * pStream,ULONGLONG * pFileTime)2761 bool FileStream_GetTime(TFileStream * pStream, ULONGLONG * pFileTime)
2762 {
2763 // Just use the saved filetime value
2764 *pFileTime = pStream->Base.File.FileTime;
2765 return true;
2766 }
2767
2768 /**
2769 * Returns the stream flags
2770 *
2771 * \a pStream Pointer to an open stream
2772 * \a pdwStreamFlags Pointer where to store the stream flags
2773 */
FileStream_GetFlags(TFileStream * pStream,LPDWORD pdwStreamFlags)2774 bool FileStream_GetFlags(TFileStream * pStream, LPDWORD pdwStreamFlags)
2775 {
2776 *pdwStreamFlags = pStream->dwFlags;
2777 return true;
2778 }
2779
2780 /**
2781 * Switches a stream with another. Used for final phase of archive compacting.
2782 * Performs these steps:
2783 *
2784 * 1) Closes the handle to the existing MPQ
2785 * 2) Renames the temporary MPQ to the original MPQ, overwrites existing one
2786 * 3) Opens the MPQ stores the handle and stream position to the new stream structure
2787 *
2788 * \a pStream Pointer to an open stream
2789 * \a pNewStream Temporary ("working") stream (created during archive compacting)
2790 */
FileStream_Replace(TFileStream * pStream,TFileStream * pNewStream)2791 bool FileStream_Replace(TFileStream * pStream, TFileStream * pNewStream)
2792 {
2793 // Only supported on flat files
2794 if((pStream->dwFlags & STREAM_PROVIDERS_MASK) != (STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE))
2795 {
2796 SetLastError(ERROR_NOT_SUPPORTED);
2797 return false;
2798 }
2799
2800 // Not supported on read-only streams
2801 if(pStream->dwFlags & STREAM_FLAG_READ_ONLY)
2802 {
2803 SetLastError(ERROR_ACCESS_DENIED);
2804 return false;
2805 }
2806
2807 // Close both stream's base providers
2808 pNewStream->BaseClose(pNewStream);
2809 pStream->BaseClose(pStream);
2810
2811 // Now we have to delete the (now closed) old file and rename the new file
2812 if(!BaseFile_Replace(pStream, pNewStream))
2813 return false;
2814
2815 // Now open the base file again
2816 if(!BaseFile_Open(pStream, pStream->szFileName, pStream->dwFlags))
2817 return false;
2818
2819 // Cleanup the new stream
2820 FileStream_Close(pNewStream);
2821 return true;
2822 }
2823
2824 /**
2825 * This function closes an archive file and frees any data buffers
2826 * that have been allocated for stream management. The function must also
2827 * support partially allocated structure, i.e. one or more buffers
2828 * can be NULL, if there was an allocation failure during the process
2829 *
2830 * \a pStream Pointer to an open stream
2831 */
FileStream_Close(TFileStream * pStream)2832 void FileStream_Close(TFileStream * pStream)
2833 {
2834 // Check if the stream structure is allocated at all
2835 if(pStream != NULL)
2836 {
2837 // Free the master stream, if any
2838 if(pStream->pMaster != NULL)
2839 FileStream_Close(pStream->pMaster);
2840 pStream->pMaster = NULL;
2841
2842 // Close the stream provider ...
2843 if(pStream->StreamClose != NULL)
2844 pStream->StreamClose(pStream);
2845
2846 // ... or close base stream, if any
2847 else if(pStream->BaseClose != NULL)
2848 pStream->BaseClose(pStream);
2849
2850 // Free the stream itself
2851 STORM_FREE(pStream);
2852 }
2853 }
2854