diff options
author | Shauren <shauren.trinity@gmail.com> | 2019-06-06 16:48:21 +0200 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2019-06-08 17:09:24 +0200 |
commit | fc330fd8ff0115804d9c4b53a1f810c00dd63de9 (patch) | |
tree | cfa10998fed66779834bf0b7a9b8b799d33d91d4 /dep/CascLib/src/common | |
parent | 82c7b6c5688495d90c4ee5995a4ff74039348296 (diff) |
Dep/CascLib: Update to ladislav-zezula/CascLib@a1197edf0b3bd4d52c3f39be7fa7b44bb0b98012
Diffstat (limited to 'dep/CascLib/src/common')
-rw-r--r-- | dep/CascLib/src/common/Array.h | 208 | ||||
-rw-r--r-- | dep/CascLib/src/common/Common.cpp | 709 | ||||
-rw-r--r-- | dep/CascLib/src/common/Common.h | 334 | ||||
-rw-r--r-- | dep/CascLib/src/common/Csv.cpp | 370 | ||||
-rw-r--r-- | dep/CascLib/src/common/Csv.h | 105 | ||||
-rw-r--r-- | dep/CascLib/src/common/Directory.cpp | 14 | ||||
-rw-r--r-- | dep/CascLib/src/common/Directory.h | 2 | ||||
-rw-r--r-- | dep/CascLib/src/common/DumpContext.cpp | 153 | ||||
-rw-r--r-- | dep/CascLib/src/common/DumpContext.h | 38 | ||||
-rw-r--r-- | dep/CascLib/src/common/DynamicArray.cpp | 101 | ||||
-rw-r--r-- | dep/CascLib/src/common/DynamicArray.h | 37 | ||||
-rw-r--r-- | dep/CascLib/src/common/FileStream.cpp | 95 | ||||
-rw-r--r-- | dep/CascLib/src/common/FileStream.h | 8 | ||||
-rw-r--r-- | dep/CascLib/src/common/FileTree.cpp | 684 | ||||
-rw-r--r-- | dep/CascLib/src/common/FileTree.h | 116 | ||||
-rw-r--r-- | dep/CascLib/src/common/ListFile.cpp | 293 | ||||
-rw-r--r-- | dep/CascLib/src/common/ListFile.h | 33 | ||||
-rw-r--r-- | dep/CascLib/src/common/Map.cpp | 287 | ||||
-rw-r--r-- | dep/CascLib/src/common/Map.h | 360 | ||||
-rw-r--r-- | dep/CascLib/src/common/RootHandler.cpp | 115 | ||||
-rw-r--r-- | dep/CascLib/src/common/RootHandler.h | 149 |
21 files changed, 2885 insertions, 1326 deletions
diff --git a/dep/CascLib/src/common/Array.h b/dep/CascLib/src/common/Array.h new file mode 100644 index 00000000000..1dc96b7be8e --- /dev/null +++ b/dep/CascLib/src/common/Array.h @@ -0,0 +1,208 @@ +/*****************************************************************************/ +/* Array.h Copyright (c) Ladislav Zezula 2015 */ +/*---------------------------------------------------------------------------*/ +/* Common array implementation */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 30.10.15 1.00 Lad The first version of DynamicArray.h */ +/* 10.08.18 1.00 Lad CLASS-ified, renamed to Array.h */ +/*****************************************************************************/ + +#ifndef __CASC_ARRAY_H__ +#define __CASC_ARRAY_H__ + +//----------------------------------------------------------------------------- +// Structures + +class CASC_ARRAY +{ + public: + + CASC_ARRAY() + { + m_pItemArray = NULL; + m_ItemCountMax = 0; + m_ItemCount = 0; + m_ItemSize = 0; + } + + ~CASC_ARRAY() + { + Free(); + } + + // Creates an array with a custom element type + template<typename TYPE> + int Create(size_t ItemCountMax) + { + return Create(sizeof(TYPE), ItemCountMax); + } + + // Creates an array with a custom element size + int Create(size_t ItemSize, size_t ItemCountMax) + { + // Create the array + if ((m_pItemArray = CASC_ALLOC(BYTE, ItemSize * ItemCountMax)) == NULL) + return ERROR_NOT_ENOUGH_MEMORY; + + m_ItemCountMax = ItemCountMax; + m_ItemCount = 0; + m_ItemSize = ItemSize; + return ERROR_SUCCESS; + } + + // Inserts one or more items; returns pointer to the first inserted item + void * Insert(size_t NewItemCount) + { + void * pNewItems; + + // Try to enlarge the buffer, if needed + if (!EnlargeArray(m_ItemCount + NewItemCount)) + return NULL; + pNewItems = m_pItemArray + (m_ItemCount * m_ItemSize); + + // Increment the size of the array + m_ItemCount += NewItemCount; + + // Return pointer to the new item + return pNewItems; + } + + // Inserts one or more items; returns pointer to the first inserted item + void * Insert(const void * NewItems, size_t NewItemCount) + { + void * pNewItem = Insert(NewItemCount); + + // Copy the item(s) to the array, if any + if (pNewItem && NewItems) + memcpy(pNewItem, NewItems, (NewItemCount * m_ItemSize)); + return pNewItem; + } + + // Returns an item at a given index + void * ItemAt(size_t ItemIndex) + { + return (ItemIndex < m_ItemCount) ? (m_pItemArray + (ItemIndex * m_ItemSize)) : NULL; + } + + void * LastItem() + { + return m_pItemArray + (m_ItemCount * m_ItemSize); + } + + // Inserts an item at a given index. If there is not enough items in the array, + // the array will be enlarged. Should any gaps to be created, the function will zero them + void * InsertAt(size_t ItemIndex) + { + LPBYTE pbLastItem; + LPBYTE pbNewItem; + + // Make sure we have array large enough + if(!EnlargeArray(ItemIndex + 1)) + return NULL; + + // Get the items range + pbLastItem = m_pItemArray + (m_ItemCount * m_ItemSize); + pbNewItem = m_pItemArray + (ItemIndex * m_ItemSize); + m_ItemCount = CASCLIB_MAX(m_ItemCount, ItemIndex+1); + + // If we inserted an item past the current end, we need to clear the items in-between + if (pbNewItem > pbLastItem) + { + memset(pbLastItem, 0, (pbNewItem - pbLastItem)); + m_ItemCount = ItemIndex + 1; + } + + return pbNewItem; + } + + // Returns index of an item + size_t IndexOf(const void * pItem) + { + LPBYTE pbItem = (LPBYTE)pItem; + + assert((m_pItemArray <= pbItem) && (pbItem <= m_pItemArray + (m_ItemCount * m_ItemSize))); + assert(((pbItem - m_pItemArray) % m_ItemSize) == 0); + + return ((pbItem - m_pItemArray) / m_ItemSize); + } + + void * ItemArray() + { + return m_pItemArray; + } + + size_t ItemCount() + { + return m_ItemCount; + } + + size_t ItemCountMax() + { + return m_ItemCountMax; + } + + size_t ItemSize() + { + return m_ItemSize; + } + + bool IsInitialized() + { + return (m_pItemArray && m_ItemCountMax); + } + + // Invalidates the entire array, but keeps memory allocated + void Reset() + { + memset(m_pItemArray, 0, m_ItemCountMax * m_ItemSize); + m_ItemCount = 0; + } + + // Frees the array + void Free() + { + CASC_FREE(m_pItemArray); + m_ItemCountMax = m_ItemCount = m_ItemSize = 0; + } + + protected: + + bool EnlargeArray(size_t NewItemCount) + { + LPBYTE NewItemArray; + size_t ItemCountMax; + + // We expect the array to be already allocated + assert(m_pItemArray != NULL); + assert(m_ItemCountMax != 0); + + // Shall we enlarge the table? + if (NewItemCount > m_ItemCountMax) + { + // Calculate new table size + ItemCountMax = m_ItemCountMax; + while (ItemCountMax < NewItemCount) + ItemCountMax = ItemCountMax << 1; + + // Allocate new table + NewItemArray = CASC_REALLOC(BYTE, m_pItemArray, (ItemCountMax * m_ItemSize)); + if (NewItemArray == NULL) + return false; + + // Set the new table size + m_ItemCountMax = ItemCountMax; + m_pItemArray = NewItemArray; + } + + return true; + } + + LPBYTE m_pItemArray; // Pointer to item array + size_t m_ItemCountMax; // Maximum item count + size_t m_ItemCount; // Current item count + size_t m_ItemSize; // Size of an item +}; + +#endif // __CASC_ARRAY__ diff --git a/dep/CascLib/src/common/Common.cpp b/dep/CascLib/src/common/Common.cpp index a0455b7c659..d545f52f84a 100644 --- a/dep/CascLib/src/common/Common.cpp +++ b/dep/CascLib/src/common/Common.cpp @@ -65,40 +65,230 @@ unsigned char IntToHexChar[] = "0123456789abcdef"; // GetLastError/SetLastError support for non-Windows platform #ifndef PLATFORM_WINDOWS -static int nLastError = ERROR_SUCCESS; +static DWORD dwLastError = ERROR_SUCCESS; -int GetLastError() +DWORD GetLastError() { - return nLastError; + return dwLastError; } -void SetLastError(int nError) +void SetLastError(DWORD dwErrCode) { - nLastError = nError; + dwLastError = dwErrCode; } #endif //----------------------------------------------------------------------------- -// String manipulation +// Overloaded "new" and "delete" operators -void CopyString(char * szTarget, const char * szSource, size_t cchLength) +void * operator new(size_t size) { - memcpy(szTarget, szSource, cchLength); - szTarget[cchLength] = 0; + return CASC_ALLOC(BYTE, size); } -void CopyString(wchar_t * szTarget, const char * szSource, size_t cchLength) +void * operator new[](size_t size) { - mbstowcs(szTarget, szSource, cchLength); - szTarget[cchLength] = 0; + return CASC_ALLOC(BYTE, size); } -void CopyString(char * szTarget, const wchar_t * szSource, size_t cchLength) +void operator delete(void * ptr) { - wcstombs(szTarget, szSource, cchLength); - szTarget[cchLength] = 0; + CASC_FREE(ptr); } +void operator delete[](void * ptr) +{ + CASC_FREE(ptr); +} + +// For some reason, VS2015 needs this +void operator delete(void * ptr, size_t) +{ + CASC_FREE(ptr); +} + +//----------------------------------------------------------------------------- +// Linear data stream manipulation + +LPBYTE CaptureInteger32(LPBYTE pbDataPtr, LPBYTE pbDataEnd, PDWORD PtrValue) +{ + // Is there enough data? + if((pbDataPtr + sizeof(DWORD)) > pbDataEnd) + return NULL; + + // Give data + PtrValue[0] = *(PDWORD)pbDataPtr; + + // Return the pointer to data following after the integer + return pbDataPtr + sizeof(DWORD); +} + +LPBYTE CaptureInteger32_BE(LPBYTE pbDataPtr, LPBYTE pbDataEnd, PDWORD PtrValue) +{ + // Is there enough data? + if((pbDataPtr + sizeof(DWORD)) > pbDataEnd) + return NULL; + + // Convert data from Little endian to + PtrValue[0] = ConvertBytesToInteger_4(pbDataPtr); + + // Return the pointer to data following after the integer + return pbDataPtr + sizeof(DWORD); +} + +LPBYTE CaptureByteArray(LPBYTE pbDataPtr, LPBYTE pbDataEnd, size_t nLength, LPBYTE pbOutput) +{ + // Is there enough data? + if((pbDataPtr + nLength) > pbDataEnd) + return NULL; + + // Give data + memcpy(pbOutput, pbDataPtr, nLength); + + // Return the pointer to data following after the integer + return pbDataPtr + nLength; +} + +LPBYTE CaptureContentKey(LPBYTE pbDataPtr, LPBYTE pbDataEnd, PCONTENT_KEY * PtrCKey) +{ + // Is there enough data? + if((pbDataPtr + sizeof(CONTENT_KEY)) > pbDataEnd) + return NULL; + + // Give data + PtrCKey[0] = (PCONTENT_KEY)pbDataPtr; + + // Return the pointer to data following after the integer + return pbDataPtr + sizeof(CONTENT_KEY); +} + +LPBYTE CaptureArray_(LPBYTE pbDataPtr, LPBYTE pbDataEnd, LPBYTE * PtrArray, size_t ItemSize, size_t ItemCount) +{ + size_t ArraySize = ItemSize * ItemCount; + + // Is there enough data? + if((pbDataPtr + ArraySize) > pbDataEnd) + return NULL; + + // Give data + PtrArray[0] = pbDataPtr; + + // Return the pointer to data following after the array + return pbDataPtr + ArraySize; +} + +//----------------------------------------------------------------------------- +// String copying and conversion + +void CascStrCopy(char * szTarget, size_t cchTarget, const char * szSource, size_t cchSource) +{ + size_t cchToCopy; + + if (cchTarget > 0) + { + // Make sure we know the length + if (cchSource == -1) + cchSource = strlen(szSource); + cchToCopy = CASCLIB_MIN((cchTarget - 1), cchSource); + + // Copy the string + memcpy(szTarget, szSource, cchToCopy); + szTarget[cchToCopy] = 0; + } +} + +void CascStrCopy(char * szTarget, size_t cchTarget, const wchar_t * szSource, size_t cchSource) +{ + size_t cchToCopy; + + if (cchTarget > 0) + { + // Make sure we know the length + if (cchSource == -1) + cchSource = wcslen(szSource); + cchToCopy = CASCLIB_MIN((cchTarget - 1), cchSource); + + wcstombs(szTarget, szSource, cchToCopy); + szTarget[cchToCopy] = 0; + } +} + +void CascStrCopy(wchar_t * szTarget, size_t cchTarget, const char * szSource, size_t cchSource) +{ + size_t cchToCopy; + + if (cchTarget > 0) + { + // Make sure we know the length + if (cchSource == -1) + cchSource = strlen(szSource); + cchToCopy = CASCLIB_MIN((cchTarget - 1), cchSource); + + mbstowcs(szTarget, szSource, cchToCopy); + szTarget[cchToCopy] = 0; + } +} + +void CascStrCopy(wchar_t * szTarget, size_t cchTarget, const wchar_t * szSource, size_t cchSource) +{ + size_t cchToCopy; + + if (cchTarget > 0) + { + // Make sure we know the length + if (cchSource == -1) + cchSource = wcslen(szSource); + cchToCopy = CASCLIB_MIN((cchTarget - 1), cchSource); + + memcpy(szTarget, szSource, cchToCopy * sizeof(wchar_t)); + szTarget[cchToCopy] = 0; + } +} + +//----------------------------------------------------------------------------- +// Safe version of s(w)printf + +size_t CascStrPrintf(char * buffer, size_t nCount, const char * format, ...) +{ + char * buffend; + va_list argList; + + // Start the argument list + va_start(argList, format); + +#ifdef PLATFORM_WINDOWS + StringCchVPrintfExA(buffer, nCount, &buffend, NULL, 0, format, argList); +#else + buffend = buffer + vsnprintf(buffer, nCount, format, argList); +#endif + + // End the argument list + va_end(argList); + return (buffend - buffer); +} + +size_t CascStrPrintf(wchar_t * buffer, size_t nCount, const wchar_t * format, ...) +{ + wchar_t * buffend; + va_list argList; + + // Start the argument list + va_start(argList, format); + +#ifdef PLATFORM_WINDOWS + StringCchVPrintfExW(buffer, nCount, &buffend, NULL, 0, format, argList); +#else + buffend = buffer + vswprintf(buffer, nCount, format, argList); +#endif + + // End the argument list + va_end(argList); + return (buffend - buffer); +} + +//----------------------------------------------------------------------------- +// String allocation + char * CascNewStr(const char * szString, size_t nCharsToReserve) { char * szNewString = NULL; @@ -137,102 +327,144 @@ wchar_t * CascNewStr(const wchar_t * szString, size_t nCharsToReserve) return szNewString; } -TCHAR * CascNewStrFromAnsi(const char * szBegin, const char * szEnd) +template <typename XCHAR> +TCHAR * AppendPathFragment(TCHAR * szBuffer, TCHAR * szBufferEnd, const XCHAR * szPath, char chSeparator, bool bFirstFragment = false) { - TCHAR * szNewString = NULL; - - // Only if the entry is valid - if(szBegin != NULL && szEnd > szBegin) + // The "Path" must not be empty + if(szPath && szPath[0]) { - // Allocate and copy the string - szNewString = CASC_ALLOC(TCHAR, (szEnd - szBegin + 1)); - if(szNewString != NULL) - CopyString(szNewString, szBegin, (szEnd - szBegin)); + // Append the path separator after the first fragment + if(szBuffer < szBufferEnd && bFirstFragment == false) + { + if(szBuffer[-1] != chSeparator) + { + *szBuffer++ = chSeparator; + } + } + + // Copy the sub path + while(szBuffer < szBufferEnd && szPath[0] != 0) + { + // If there is a path separator, we skip it (all of them) and put single separator there + if(szPath[0] == '\\' || szPath[0] == '/') + { + while(szPath[0] == '\\' || szPath[0] == '/') + szPath++; + *szBuffer++ = chSeparator; + } + else + { + *szBuffer++ = *szPath++; + } + } + + // Append end of string + szBuffer[0] = 0; } - // Return the string - return szNewString; + return szBuffer; } -TCHAR * CombinePath(const TCHAR * szDirectory, const TCHAR * szSubDir) +TCHAR * GetLastPathPart(TCHAR * szWorkPath) { - TCHAR * szFullPath = NULL; - TCHAR * szPathPtr; - size_t nLength1 = 0; - size_t nLength2 = 0; + size_t nLength = _tcslen(szWorkPath); - // Calculate lengths of each part - if(szDirectory != NULL) - { - // Get the length of the directory - nLength1 = _tcslen(szDirectory); + // Go one character back + if(nLength > 0) + nLength--; - // Cut all ending backslashes - while(nLength1 > 0 && (szDirectory[nLength1 - 1] == _T('\\') || szDirectory[nLength1 - 1] == _T('/'))) - nLength1--; - } + // Cut ending (back)slashes, if any + while(nLength > 0 && (szWorkPath[nLength] == _T('\\') || szWorkPath[nLength] == _T('/'))) + nLength--; - if(szSubDir != NULL) + // Cut the last path part + while(nLength > 0) { - // Cut all leading backslashes - while(szSubDir[0] == _T(PATH_SEPARATOR)) - szSubDir++; - - // Get the length of the subdir - nLength2 = _tcslen(szSubDir); - - // Cut all ending backslashes - while(nLength2 > 0 && szSubDir[nLength2 - 1] == _T(PATH_SEPARATOR)) - nLength2--; - } - - // Allocate space for the full path - szFullPath = szPathPtr = CASC_ALLOC(TCHAR, nLength1 + nLength2 + 2); - if(szFullPath != NULL) - { - // Copy the directory - if(szDirectory != NULL && nLength1 != 0) + // End of path? + if(szWorkPath[nLength] == _T('\\') || szWorkPath[nLength] == _T('/')) { - memcpy(szPathPtr, szDirectory, (nLength1 * sizeof(TCHAR))); - szPathPtr += nLength1; + return szWorkPath + nLength; } - // Copy the sub-directory - if(szSubDir != NULL && nLength2 != 0) - { - // Append backslash to the previous one - if(szPathPtr > szFullPath) - *szPathPtr++ = _T(PATH_SEPARATOR); + // Go one character back + nLength--; + } - // Copy the string - memcpy(szPathPtr, szSubDir, (nLength2 * sizeof(TCHAR))); - szPathPtr += nLength2; - } + return NULL; +} - // Terminate the string - szPathPtr[0] = 0; - } +bool CutLastPathPart(TCHAR * szWorkPath) +{ + // Get the last part of the path + szWorkPath = GetLastPathPart(szWorkPath); + if(szWorkPath == NULL) + return false; - return szFullPath; + szWorkPath[0] = 0; + return true; } -TCHAR * CombinePathAndString(const TCHAR * szPath, const char * szString, size_t nLength) +TCHAR * CombinePath(const TCHAR * szDirectory, const TCHAR * szSubDir) { + TCHAR * szFullPathEnd; TCHAR * szFullPath = NULL; - TCHAR * szSubDir; + TCHAR * szPathPtr; + size_t nLength1 = (szDirectory != NULL) ? _tcslen(szDirectory) : 0; + size_t nLength2 = (szSubDir != NULL) ? _tcslen(szSubDir) : 0; - // Create the subdir string - szSubDir = CASC_ALLOC(TCHAR, nLength + 1); - if(szSubDir != NULL) + // Allocate the entire buffer + szFullPath = szPathPtr = CASC_ALLOC(TCHAR, nLength1 + nLength2 + 2); + szFullPathEnd = szFullPath + nLength1 + nLength2 + 1; + if(szFullPath != NULL) { - CopyString(szSubDir, szString, nLength); - szFullPath = CombinePath(szPath, szSubDir); - CASC_FREE(szSubDir); + szPathPtr = AppendPathFragment(szPathPtr, szFullPathEnd, szDirectory, PATH_SEP_CHAR, true); + szPathPtr = AppendPathFragment(szPathPtr, szFullPathEnd, szSubDir, PATH_SEP_CHAR); } return szFullPath; } +size_t CombineFilePath(TCHAR * szBuffer, size_t nMaxChars, const TCHAR * szPath, const TCHAR * szSubPath1, const TCHAR * szSubPath2) +{ + TCHAR * szSaveBuffer = szBuffer; + TCHAR * szBufferEnd = szBuffer + nMaxChars - 1; + + // Append all three parts and return length + szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szPath, PATH_SEP_CHAR, true); + szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szSubPath1, PATH_SEP_CHAR); + szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szSubPath2, PATH_SEP_CHAR); + return (szBuffer - szSaveBuffer); +} + +size_t CombineUrlPath(TCHAR * szBuffer, size_t nMaxChars, const TCHAR * szHost, const TCHAR * szSubPath1, const TCHAR * szSubPath2) +{ + TCHAR * szSaveBuffer = szBuffer; + TCHAR * szBufferEnd = szBuffer + nMaxChars - 1; + + // Append all three parts and return length + szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szHost, '/', true); + szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szSubPath1, '/'); + szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szSubPath2, '/'); + return (szBuffer - szSaveBuffer); +} + +size_t CreateCascSubdirectoryName(TCHAR * szBuffer, size_t nMaxChars, const TCHAR * szSubDir, const TCHAR * szExtension, LPBYTE pbEKey) +{ + TCHAR * szSaveBuffer = szBuffer; + TCHAR * szBufferEnd = szBuffer + nMaxChars - 1; + char szHashSubPath[0x80]; + char szHashText[MD5_STRING_SIZE+1]; + + // Prepare the subpath + StringFromBinary(pbEKey, MD5_HASH_SIZE, szHashText); + CascStrPrintf(szHashSubPath, _countof(szHashSubPath), "%02x/%02x/%s", pbEKey[0], pbEKey[1], szHashText); + + szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szSubDir, '/', true); + szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szHashSubPath, '/'); + szBuffer = AppendPathFragment(szBuffer, szBufferEnd, szExtension, '/', true); + return (szBuffer - szSaveBuffer); +} + size_t NormalizeFileName(const unsigned char * NormTable, char * szNormName, const char * szFileName, size_t cchMaxChars) { char * szNormNameEnd = szNormName + cchMaxChars; @@ -257,19 +489,26 @@ size_t NormalizeFileName_LowerSlash(char * szNormName, const char * szFileName, return NormalizeFileName(AsciiToLowerTable_Slash, szNormName, szFileName, cchMaxChars); } -ULONGLONG CalcFileNameHash(const char * szFileName) +ULONGLONG CalcNormNameHash(const char * szNormName, size_t nLength) { - char szNormName[MAX_PATH+1]; uint32_t dwHashHigh = 0; uint32_t dwHashLow = 0; + + // Calculate the HASH value of the normalized file name + hashlittle2(szNormName, nLength, &dwHashHigh, &dwHashLow); + return ((ULONGLONG)dwHashHigh << 0x20) | dwHashLow; +} + +ULONGLONG CalcFileNameHash(const char * szFileName) +{ + char szNormName[MAX_PATH+1]; size_t nLength; // Normalize the file name - convert to uppercase, slashes to backslashes nLength = NormalizeFileName_UpperBkSlash(szNormName, szFileName, MAX_PATH); - // Calculate the HASH value of the normalized file name - hashlittle2(szNormName, nLength, &dwHashHigh, &dwHashLow); - return ((ULONGLONG)dwHashHigh << 0x20) | dwHashLow; + // Calculate hash from the normalized name + return CalcNormNameHash(szNormName, nLength); } int ConvertDigitToInt32(const TCHAR * szString, PDWORD PtrValue) @@ -373,13 +612,16 @@ char * StringFromBinary(LPBYTE pbBinary, size_t cbBinary, char * szBuffer) { char * szSaveBuffer = szBuffer; - // Convert the string to the array of MD5 - // Copy the blob data as text - for(size_t i = 0; i < cbBinary; i++) + // Verify the binary pointer + if(pbBinary && cbBinary) { - *szBuffer++ = IntToHexChar[pbBinary[0] >> 0x04]; - *szBuffer++ = IntToHexChar[pbBinary[0] & 0x0F]; - pbBinary++; + // Convert the string to the array of MD5 + // Copy the blob data as text + for(size_t i = 0; i < cbBinary; i++) + { + *szBuffer++ = IntToHexChar[pbBinary[i] >> 0x04]; + *szBuffer++ = IntToHexChar[pbBinary[i] & 0x0F]; + } } // Terminate the string @@ -395,39 +637,51 @@ char * StringFromMD5(LPBYTE md5, char * szBuffer) //----------------------------------------------------------------------------- // File name utilities -const wchar_t * GetPlainFileName(const wchar_t * szFileName) +bool IsFileDataIdName(const char * szFileName, DWORD & FileDataId) { - const wchar_t * szPlainName = szFileName; + BYTE BinaryValue[4]; - while(*szFileName != 0) + // File name must begin with "File", case insensitive + if(AsciiToUpperTable_BkSlash[szFileName[0]] == 'F' && + AsciiToUpperTable_BkSlash[szFileName[1]] == 'I' && + AsciiToUpperTable_BkSlash[szFileName[2]] == 'L' && + AsciiToUpperTable_BkSlash[szFileName[3]] == 'E') { - if(*szFileName == '\\' || *szFileName == '/') - szPlainName = szFileName + 1; - szFileName++; + // Then, 8 hexadecimal digits must follow + if(ConvertStringToBinary(szFileName + 4, 8, BinaryValue) == ERROR_SUCCESS) + { + // Must be followed by an extension or end-of-string + if(szFileName[0x0C] == 0 || szFileName[0x0C] == '.') + { + FileDataId = ConvertBytesToInteger_4(BinaryValue); + return (FileDataId != CASC_INVALID_ID); + } + } } - return szPlainName; + return false; } -const char * GetPlainFileName(const char * szFileName) +bool IsFileCKeyEKeyName(const char * szFileName, LPBYTE PtrKeyBuffer) { - const char * szPlainName = szFileName; + size_t nLength = strlen(szFileName); - while(*szFileName != 0) + if(nLength == MD5_STRING_SIZE) { - if(*szFileName == '\\' || *szFileName == '/') - szPlainName = szFileName + 1; - szFileName++; + if(ConvertStringToBinary(szFileName, MD5_STRING_SIZE, PtrKeyBuffer) == ERROR_SUCCESS) + { + return true; + } } - return szPlainName; + return false; } -bool CheckWildCard(const char * szString, const char * szWildCard) +bool CascCheckWildCard(const char * szString, const char * szWildCard) { const char * szWildCardPtr; - for(;;) + while(szWildCard && szWildCard[0]) { // If there is '?' in the wildcard, we skip one char while(szWildCard[0] == '?') @@ -455,7 +709,7 @@ bool CheckWildCard(const char * szString, const char * szWildCard) if(AsciiToUpperTable_BkSlash[szWildCardPtr[0]] == AsciiToUpperTable_BkSlash[szString[0]]) { - if(CheckWildCard(szString, szWildCardPtr)) + if(CascCheckWildCard(szString, szWildCardPtr)) return true; } } @@ -476,244 +730,43 @@ bool CheckWildCard(const char * szString, const char * szWildCard) return (szString[0] == 0) ? true : false; } } + return true; } //----------------------------------------------------------------------------- // Hashing functions -bool IsValidMD5(LPBYTE pbMd5) +bool CascIsValidMD5(LPBYTE pbMd5) { - BYTE BitSummary = 0; + PDWORD Int32Array = (PDWORD)pbMd5; - // The MD5 is considered invalid of it is zeroed - BitSummary |= pbMd5[0x00] | pbMd5[0x01] | pbMd5[0x02] | pbMd5[0x03] | pbMd5[0x04] | pbMd5[0x05] | pbMd5[0x06] | pbMd5[0x07]; - BitSummary |= pbMd5[0x08] | pbMd5[0x09] | pbMd5[0x0A] | pbMd5[0x0B] | pbMd5[0x0C] | pbMd5[0x0D] | pbMd5[0x0E] | pbMd5[0x0F]; - return (BitSummary != 0); + // The MD5 is considered invalid if it is zeroed + return (Int32Array[0] | Int32Array[1] | Int32Array[2] | Int32Array[3]) ? true : false; } -bool VerifyDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE expected_md5) +bool CascVerifyDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE expected_md5) { - hash_state md5_state; + MD5_CTX md5_ctx; BYTE md5_digest[MD5_HASH_SIZE]; // Don't verify the block if the MD5 is not valid. - if(!IsValidMD5(expected_md5)) + if(!CascIsValidMD5(expected_md5)) return true; // Calculate the MD5 of the data block - md5_init(&md5_state); - md5_process(&md5_state, (unsigned char *)pvDataBlock, cbDataBlock); - md5_done(&md5_state, md5_digest); + MD5_Init(&md5_ctx); + MD5_Update(&md5_ctx, pvDataBlock, cbDataBlock); + MD5_Final(md5_digest, &md5_ctx); // Does the MD5's match? return (memcmp(md5_digest, expected_md5, MD5_HASH_SIZE) == 0); } -void CalculateDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE md5_hash) -{ - hash_state md5_state; - - md5_init(&md5_state); - md5_process(&md5_state, (unsigned char *)pvDataBlock, cbDataBlock); - md5_done(&md5_state, md5_hash); -} - -//----------------------------------------------------------------------------- -// We have our own qsort implementation, optimized for using array of pointers - -#define STKSIZ (8*sizeof(void*) - 2) - -#define SWAP_ENTRIES(index1, index2) \ -{ \ - temp = base[index1]; \ - base[index1] = base[index2]; \ - base[index2] = temp; \ -} - -void qsort_pointer_array(void ** base, size_t num, int (*compare)(const void *, const void *, const void *), const void * context) +void CascCalculateDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE md5_hash) { - size_t lo, hi; /* ends of sub-array currently sorting */ - size_t mid; /* points to middle of subarray */ - size_t loguy, higuy; /* traveling pointers for partition step */ - size_t size; /* size of the sub-array */ - size_t lostk[STKSIZ], histk[STKSIZ]; - void * temp; - int stkptr; /* stack for saving sub-array to be processed */ - - /* validation section */ - assert(base != NULL); - assert(compare != NULL); - - if (num < 2) - return; /* nothing to do */ - - stkptr = 0; /* initialize stack */ - - lo = 0; - hi = (num-1); /* initialize limits */ - - /* this entry point is for pseudo-recursion calling: setting - lo and hi and jumping to here is like recursion, but stkptr is - preserved, locals aren't, so we preserve stuff on the stack */ -recurse: - - size = (hi - lo) + 1; /* number of el's to sort */ - - /* First we pick a partitioning element. The efficiency of the - algorithm demands that we find one that is approximately the median - of the values, but also that we select one fast. We choose the - median of the first, middle, and last elements, to avoid bad - performance in the face of already sorted data, or data that is made - up of multiple sorted runs appended together. Testing shows that a - median-of-three algorithm provides better performance than simply - picking the middle element for the latter case. */ - - mid = lo + (size / 2); /* find middle element */ - - /* Sort the first, middle, last elements into order */ - if (compare(context, base[lo], base[mid]) > 0) { - SWAP_ENTRIES(lo, mid); - } - if (compare(context, base[lo], base[hi]) > 0) { - SWAP_ENTRIES(lo, hi); - } - if (compare(context, base[mid], base[hi]) > 0) { - SWAP_ENTRIES(mid, hi); - } - - /* We now wish to partition the array into three pieces, one consisting - of elements <= partition element, one of elements equal to the - partition element, and one of elements > than it. This is done - below; comments indicate conditions established at every step. */ - - loguy = lo; - higuy = hi; - - /* Note that higuy decreases and loguy increases on every iteration, - so loop must terminate. */ - for (;;) { - /* lo <= loguy < hi, lo < higuy <= hi, - A[i] <= A[mid] for lo <= i <= loguy, - A[i] > A[mid] for higuy <= i < hi, - A[hi] >= A[mid] */ - - /* The doubled loop is to avoid calling comp(mid,mid), since some - existing comparison funcs don't work when passed the same - value for both pointers. */ - - if (mid > loguy) { - do { - loguy ++; - } while (loguy < mid && compare(context, base[loguy], base[mid]) <= 0); - } - if (mid <= loguy) { - do { - loguy ++; - } while (loguy <= hi && compare(context, base[loguy], base[mid]) <= 0); - } - - /* lo < loguy <= hi+1, A[i] <= A[mid] for lo <= i < loguy, - either loguy > hi or A[loguy] > A[mid] */ - - do { - higuy --; - } while (higuy > mid && compare(context, base[higuy], base[mid]) > 0); - - /* lo <= higuy < hi, A[i] > A[mid] for higuy < i < hi, - either higuy == lo or A[higuy] <= A[mid] */ - - if (higuy < loguy) - break; - - /* if loguy > hi or higuy == lo, then we would have exited, so - A[loguy] > A[mid], A[higuy] <= A[mid], - loguy <= hi, higuy > lo */ - - SWAP_ENTRIES(loguy, higuy); - - /* If the partition element was moved, follow it. Only need - to check for mid == higuy, since before the swap, - A[loguy] > A[mid] implies loguy != mid. */ - - if (mid == higuy) - mid = loguy; - - /* A[loguy] <= A[mid], A[higuy] > A[mid]; so condition at top - of loop is re-established */ - } + MD5_CTX md5_ctx; - /* A[i] <= A[mid] for lo <= i < loguy, - A[i] > A[mid] for higuy < i < hi, - A[hi] >= A[mid] - higuy < loguy - implying: - higuy == loguy-1 - or higuy == hi - 1, loguy == hi + 1, A[hi] == A[mid] */ - - /* Find adjacent elements equal to the partition element. The - doubled loop is to avoid calling comp(mid,mid), since some - existing comparison funcs don't work when passed the same value - for both pointers. */ - - higuy ++; - if (mid < higuy) { - do { - higuy --; - } while (higuy > mid && compare(context, base[higuy], base[mid]) == 0); - } - if (mid >= higuy) { - do { - higuy --; - } while (higuy > lo && compare(context, base[higuy], base[mid]) == 0); - } - - /* OK, now we have the following: - higuy < loguy - lo <= higuy <= hi - A[i] <= A[mid] for lo <= i <= higuy - A[i] == A[mid] for higuy < i < loguy - A[i] > A[mid] for loguy <= i < hi - A[hi] >= A[mid] */ - - /* We've finished the partition, now we want to sort the subarrays - [lo, higuy] and [loguy, hi]. - We do the smaller one first to minimize stack usage. - We only sort arrays of length 2 or more.*/ - - if ( higuy - lo >= hi - loguy ) { - if (lo < higuy) { - lostk[stkptr] = lo; - histk[stkptr] = higuy; - ++stkptr; - } /* save big recursion for later */ - - if (loguy < hi) { - lo = loguy; - goto recurse; /* do small recursion */ - } - } - else { - if (loguy < hi) { - lostk[stkptr] = loguy; - histk[stkptr] = hi; - ++stkptr; /* save big recursion for later */ - } - - if (lo < higuy) { - hi = higuy; - goto recurse; /* do small recursion */ - } - } - - /* We have sorted the array, except for any pending sorts on the stack. - Check if there are any, and do them. */ - - --stkptr; - if (stkptr >= 0) { - lo = lostk[stkptr]; - hi = histk[stkptr]; - goto recurse; /* pop subarray from stack */ - } - else - return; /* all subarrays done */ + MD5_Init(&md5_ctx); + MD5_Update(&md5_ctx, pvDataBlock, cbDataBlock); + MD5_Final(md5_hash, &md5_ctx); } diff --git a/dep/CascLib/src/common/Common.h b/dep/CascLib/src/common/Common.h index 484276a3bd7..c7f189b91ac 100644 --- a/dep/CascLib/src/common/Common.h +++ b/dep/CascLib/src/common/Common.h @@ -21,6 +21,76 @@ #define ALIGN_TO_SIZE(x, a) (((x) + (a)-1) & ~((a)-1)) #endif +// Prevent problems with CRT "min" and "max" functions, +// as they are not defined on all platforms +#define CASCLIB_MIN(a, b) ((a < b) ? a : b) +#define CASCLIB_MAX(a, b) ((a > b) ? a : b) +#define CASCLIB_UNUSED(p) ((void)(p)) + +//----------------------------------------------------------------------------- +// Common structures + +// Structure for static content key (CKey) and encoded key (EKey) +// The CKey is a MD5 hash of the file data. +// The EKey is (shortened) MD5 hash of the file header, which contains MD5 hashes of all the logical blocks of the file. +typedef struct _CONTENT_KEY +{ + BYTE Value[MD5_HASH_SIZE]; // MD5 of the file + +} CONTENT_KEY, *PCONTENT_KEY, ENCODED_KEY, *PENCODED_KEY; + +// Helper structure for merging file paths +typedef struct _PATH_BUFFER +{ + char * szBegin; + char * szPtr; + char * szEnd; +} PATH_BUFFER, *PPATH_BUFFER; + +//----------------------------------------------------------------------------- +// Basic structure used by all CascLib objects to describe a single entry +// in the CASC storage. Each entry represents one physical file +// in the storage. Note that the file may be present under several file names. + +// Flags for CASC_CKEY_ENTRY::Flags +#define CASC_CE_FILE_IS_LOCAL 0x00000001 // The file is available locally. Keep this flag to have value of 1 +#define CASC_CE_HAS_CKEY 0x00000002 // The CKey is present in the entry +#define CASC_CE_HAS_EKEY 0x00000004 // The EKey is present, at least partial one +#define CASC_CE_HAS_EKEY_PARTIAL 0x00000008 // The EKey is only partial, padded by zeros. Always used with CASC_CE_HAS_EKEY +#define CASC_CE_IN_ENCODING 0x00000010 // Present in the ENCODING manifest +#define CASC_CE_IN_DOWNLOAD 0x00000020 // Present in the DOWNLOAD manifest +#define CASC_CE_FOLDER_ENTRY 0x00000040 // This CKey entry is a folder + +// In-memory representation of a single entry. +struct CASC_CKEY_ENTRY +{ + CASC_CKEY_ENTRY() + { + Init(); + } + + void Init(void) + { + memset(this, 0, sizeof(CASC_CKEY_ENTRY)); + StorageOffset = CASC_INVALID_OFFS64; + EncodedSize = CASC_INVALID_SIZE; + ContentSize = CASC_INVALID_SIZE; + } + + BYTE CKey[MD5_HASH_SIZE]; // Content key of the full length + BYTE EKey[MD5_HASH_SIZE]; // Encoded key of the full length + ULONGLONG StorageOffset; // Linear offset over the entire storage. 0 if not present + ULONGLONG TagBitMask; // Bitmap for the tags. 0 ig tags are not supported + DWORD EncodedSize; // Encoded size of the file. 0 if not supported + DWORD ContentSize; // Content size of the file. 0 if not supported + DWORD Flags; // See CASC_CE_XXX + USHORT RefCount; // This is the number of file names referencing this entry + BYTE Priority; // Download priority + BYTE Alignment; + +}; +typedef CASC_CKEY_ENTRY *PCASC_CKEY_ENTRY; + //----------------------------------------------------------------------------- // Conversion tables @@ -29,23 +99,198 @@ extern unsigned char AsciiToUpperTable_BkSlash[256]; extern unsigned char IntToHexChar[]; //----------------------------------------------------------------------------- -// String manipulation +// Memory management +// +// We use our own macros for allocating/freeing memory. If you want +// to redefine them, please keep the following rules: +// +// - The memory allocation must return NULL if not enough memory +// (i.e not to throw exception) +// - The allocating function does not need to fill the allocated buffer with zeros +// - The reallocating function must support NULL as the previous block +// - Memory freeing function must check for NULL pointer and do nothing if so +// + +#define CASC_REALLOC(type, ptr, count) (type *)realloc(ptr, (count) * sizeof(type)) +#define CASC_ALLOC(type, count) (type *)malloc((count) * sizeof(type)) + +template <typename T> +void CASC_FREE(T *& ptr) +{ + if (ptr != NULL) + free(ptr); + ptr = NULL; +} + +//----------------------------------------------------------------------------- +// Overloaded "new" and "delete" operators + +void * operator new(size_t size); +void * operator new[](size_t size); +void operator delete(void * ptr); +void operator delete[](void * ptr); +void operator delete(void * ptr, size_t); // For some reason, VS2015 needs this + +//----------------------------------------------------------------------------- +// Big endian number manipulation + +inline DWORD ConvertBytesToInteger_2(LPBYTE ValueAsBytes) +{ + USHORT Value = 0; + + Value = (Value << 0x08) | ValueAsBytes[0]; + Value = (Value << 0x08) | ValueAsBytes[1]; + + return Value; +} + +inline DWORD ConvertBytesToInteger_3(LPBYTE ValueAsBytes) +{ + DWORD Value = 0; + + Value = (Value << 0x08) | ValueAsBytes[0]; + Value = (Value << 0x08) | ValueAsBytes[1]; + Value = (Value << 0x08) | ValueAsBytes[2]; + + return Value; +} + +inline DWORD ConvertBytesToInteger_4(LPBYTE ValueAsBytes) +{ + DWORD Value = 0; + + Value = (Value << 0x08) | ValueAsBytes[0]; + Value = (Value << 0x08) | ValueAsBytes[1]; + Value = (Value << 0x08) | ValueAsBytes[2]; + Value = (Value << 0x08) | ValueAsBytes[3]; + + return Value; +} + +// Converts the variable-size big-endian into integer +inline DWORD ConvertBytesToInteger_X(LPBYTE ValueAsBytes, DWORD dwByteSize) +{ + DWORD Value = 0; + + if(dwByteSize > 0) + Value = (Value << 0x08) | ValueAsBytes[0]; + if(dwByteSize > 1) + Value = (Value << 0x08) | ValueAsBytes[1]; + if(dwByteSize > 2) + Value = (Value << 0x08) | ValueAsBytes[2]; + if(dwByteSize > 3) + Value = (Value << 0x08) | ValueAsBytes[3]; + + return Value; +} + +inline DWORD ConvertBytesToInteger_4_LE(LPBYTE ValueAsBytes) +{ + DWORD Value = 0; + + Value = (Value << 0x08) | ValueAsBytes[3]; + Value = (Value << 0x08) | ValueAsBytes[2]; + Value = (Value << 0x08) | ValueAsBytes[1]; + Value = (Value << 0x08) | ValueAsBytes[0]; + + return Value; +} + +// Read the 40-bit big-endian offset into ULONGLONG +inline ULONGLONG ConvertBytesToInteger_5(LPBYTE ValueAsBytes) +{ + ULONGLONG Value = 0; + + Value = (Value << 0x08) | ValueAsBytes[0]; + Value = (Value << 0x08) | ValueAsBytes[1]; + Value = (Value << 0x08) | ValueAsBytes[2]; + Value = (Value << 0x08) | ValueAsBytes[3]; + Value = (Value << 0x08) | ValueAsBytes[4]; + + return Value; +} + +inline void ConvertIntegerToBytes_4(DWORD Value, LPBYTE ValueAsBytes) +{ + ValueAsBytes[0] = (Value >> 0x18) & 0xFF; + ValueAsBytes[1] = (Value >> 0x10) & 0xFF; + ValueAsBytes[2] = (Value >> 0x08) & 0xFF; + ValueAsBytes[3] = (Value >> 0x00) & 0xFF; +} + +inline void ConvertIntegerToBytes_4_LE(DWORD Value, LPBYTE ValueAsBytes) +{ + ValueAsBytes[0] = (Value >> 0x00) & 0xFF; + ValueAsBytes[1] = (Value >> 0x08) & 0xFF; + ValueAsBytes[2] = (Value >> 0x10) & 0xFF; + ValueAsBytes[3] = (Value >> 0x18) & 0xFF; +} + +// Faster than memset(Buffer, 0, 0x10) +inline void ZeroMemory16(void * Buffer) +{ + PDWORD PtrBuffer = (PDWORD)Buffer; + + PtrBuffer[0] = 0; + PtrBuffer[1] = 0; + PtrBuffer[2] = 0; + PtrBuffer[3] = 0; +} + +// Faster than memcpy(Target, Source, 0x10) +inline void CopyMemory16(void * Target, void * Source) +{ + PDWORD PtrTarget = (PDWORD)Target; + PDWORD PtrSource = (PDWORD)Source; + + PtrTarget[0] = PtrSource[0]; + PtrTarget[1] = PtrSource[1]; + PtrTarget[2] = PtrSource[2]; + PtrTarget[3] = PtrSource[3]; +} + +//----------------------------------------------------------------------------- +// Linear data stream manipulation -void CopyString(char * szTarget, const char * szSource, size_t cchLength); -void CopyString(wchar_t * szTarget, const char * szSource, size_t cchLength); -void CopyString(char * szTarget, const wchar_t * szSource, size_t cchLength); +LPBYTE CaptureInteger32(LPBYTE pbDataPtr, LPBYTE pbDataEnd, PDWORD PtrValue); +LPBYTE CaptureInteger32_BE(LPBYTE pbDataPtr, LPBYTE pbDataEnd, PDWORD PtrValue); +LPBYTE CaptureByteArray(LPBYTE pbDataPtr, LPBYTE pbDataEnd, size_t nLength, LPBYTE pbOutput); +LPBYTE CaptureContentKey(LPBYTE pbDataPtr, LPBYTE pbDataEnd, PCONTENT_KEY * PtrCKey); +LPBYTE CaptureArray_(LPBYTE pbDataPtr, LPBYTE pbDataEnd, LPBYTE * PtrArray, size_t ItemSize, size_t ItemCount); -char * CascNewStr(const char * szString, size_t nCharsToReserve); -wchar_t * CascNewStr(const wchar_t * szString, size_t nCharsToReserve); +#define CaptureArray(pbDataPtr, pbDataEnd, PtrArray, type, count) CaptureArray_(pbDataPtr, pbDataEnd, PtrArray, sizeof(type), count) -TCHAR * CascNewStrFromAnsi(const char * szBegin, const char * szEnd); +//----------------------------------------------------------------------------- +// String copying and conversion + +void CascStrCopy(char * szTarget, size_t cchTarget, const char * szSource, size_t cchSource = -1); +void CascStrCopy(char * szTarget, size_t cchTarget, const wchar_t * szSource, size_t cchSource = -1); +void CascStrCopy(wchar_t * szTarget, size_t cchTarget, const char * szSource, size_t cchSource = -1); +void CascStrCopy(wchar_t * szTarget, size_t cchTarget, const wchar_t * szSource, size_t cchSource = -1); + +//----------------------------------------------------------------------------- +// Safe version of s(w)printf + +size_t CascStrPrintf(char * buffer, size_t nCount, const char * format, ...); +size_t CascStrPrintf(wchar_t * buffer, size_t nCount, const wchar_t * format, ...); + +//----------------------------------------------------------------------------- +// String allocation + +char * CascNewStr(const char * szString, size_t nCharsToReserve = 0); +wchar_t * CascNewStr(const wchar_t * szString, size_t nCharsToReserve = 0); TCHAR * CombinePath(const TCHAR * szPath, const TCHAR * szSubDir); -TCHAR * CombinePathAndString(const TCHAR * szPath, const char * szString, size_t nLength); +size_t CombineFilePath(TCHAR * szBuffer, size_t nMaxChars, const TCHAR * szPath, const TCHAR * szSubPath1, const TCHAR * szSubPath2 = NULL); +size_t CombineUrlPath(TCHAR * szBuffer, size_t nMaxChars, const TCHAR * szHost, const TCHAR * szSubPath1, const TCHAR * szSubPath2 = NULL); +TCHAR * GetLastPathPart(TCHAR * szWorkPath); +bool CutLastPathPart(TCHAR * szWorkPath); +size_t CreateCascSubdirectoryName(TCHAR * szBuffer, size_t nMaxChars, const TCHAR * szSubDir, const TCHAR * szExtension, LPBYTE pbEKey); size_t NormalizeFileName_UpperBkSlash(char * szNormName, const char * szFileName, size_t cchMaxChars); size_t NormalizeFileName_LowerSlash(char * szNormName, const char * szFileName, size_t cchMaxChars); +ULONGLONG CalcNormNameHash(const char * szNormName, size_t nLength); ULONGLONG CalcFileNameHash(const char * szFileName); int ConvertDigitToInt32(const TCHAR * szString, PDWORD PtrValue); @@ -56,20 +301,81 @@ char * StringFromBinary(LPBYTE pbBinary, size_t cbBinary, char * szBuffer); char * StringFromMD5(LPBYTE md5, char * szBuffer); //----------------------------------------------------------------------------- +// Structure query key + +struct QUERY_KEY +{ + QUERY_KEY() + { + pbData = NULL; + cbData = 0; + } + + ~QUERY_KEY() + { + CASC_FREE(pbData); + cbData = 0; + } + + LPBYTE pbData; + size_t cbData; +}; +typedef QUERY_KEY *PQUERY_KEY; + +//----------------------------------------------------------------------------- // File name utilities -bool CheckWildCard(const char * szString, const char * szWildCard); -const wchar_t * GetPlainFileName(const wchar_t * szFileName); -const char * GetPlainFileName(const char * szFileName); +// Retrieves the pointer to plain name +template <typename XCHAR> +const XCHAR * GetPlainFileName(const XCHAR * szFileName) +{ + const XCHAR * szPlainName = szFileName; + + while(*szFileName != 0) + { + if(*szFileName == '\\' || *szFileName == '/') + szPlainName = szFileName + 1; + szFileName++; + } + + return szPlainName; +} + +// Retrieves the pointer to file extension +template <typename XCHAR> +const XCHAR * GetFileExtension(const XCHAR * szFileName) +{ + const XCHAR * szExtension = NULL; + + // We need to start searching from the plain name + // Avoid: C:\$RECYCLE.BIN\File.ext + szFileName = GetPlainFileName(szFileName); + + // Find the last dot in the plain file name + while(szFileName[0] != 0) + { + if(szFileName[0] == '.') + szExtension = szFileName; + szFileName++; + } + + // If not found, return the end of the file name + return (XCHAR *)((szExtension != NULL) ? szExtension : szFileName); +} + +bool IsFileDataIdName(const char * szFileName, DWORD & FileDataId); +bool IsFileCKeyEKeyName(const char * szFileName, LPBYTE PtrKeyBuffer); + +bool CascCheckWildCard(const char * szString, const char * szWildCard); //----------------------------------------------------------------------------- // Hashing functions ULONGLONG HashStringJenkins(const char * szFileName); -bool IsValidMD5(LPBYTE pbMd5); -void CalculateDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE md5_hash); -bool VerifyDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE expected_md5); +bool CascIsValidMD5(LPBYTE pbMd5); +void CascCalculateDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE md5_hash); +bool CascVerifyDataBlockHash(void * pvDataBlock, DWORD cbDataBlock, LPBYTE expected_md5); //----------------------------------------------------------------------------- // Scanning a directory diff --git a/dep/CascLib/src/common/Csv.cpp b/dep/CascLib/src/common/Csv.cpp new file mode 100644 index 00000000000..24b654daa86 --- /dev/null +++ b/dep/CascLib/src/common/Csv.cpp @@ -0,0 +1,370 @@ +/*****************************************************************************/ +/* Csv.cpp Copyright (c) Ladislav Zezula 2019 */ +/*---------------------------------------------------------------------------*/ +/* Implementation for the CSV handler class */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 24.05.19 1.00 Lad Created */ +/*****************************************************************************/ + +#define __CASCLIB_SELF__ +#include "../CascLib.h" +#include "../CascCommon.h" + +//----------------------------------------------------------------------------- +// Local variables + +static const CASC_CSV_LINE NullLine; +#define NullColumn NullLine.Columns[0]; + +//----------------------------------------------------------------------------- +// Local functions + +static char * NextLine(char * szLine) +{ + // Find the end of the line + while(szLine[0] != 0 && szLine[0] != 0x0A && szLine[0] != 0x0D) + szLine++; + + // Terminate the line + while(szLine[0] == 0x0A || szLine[0] == 0x0D) + *szLine++ = 0; + + // If there's an end-of-string, it's over + return (szLine[0] != 0) ? szLine : NULL; +} + +static char * NextColumn(char * szColumn) +{ + // Find the end of the column + while(szColumn[0] != 0 && szColumn[0] != '|') + szColumn++; + + // Terminate the column + if (szColumn[0] == '|') + { + *szColumn++ = 0; + return szColumn; + } + + // If there's an end-of-string, it's over + return NULL; +} + +static size_t CalcHashValue(const char * szString) +{ + size_t dwHash = 0x7EEE7EEE; + + // Hash the string itself + while(szString[0] != 0) + { + dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ szString[0]; + szString++; + } + + // Return the hash limited by the table size + return dwHash; +} + +static BYTE HashToIndex(size_t dwHashValue) +{ + return (BYTE)(dwHashValue & (CSV_HASH_TABLE_SIZE - 1)); +} + +//----------------------------------------------------------------------------- +// Class for CSV line + +CASC_CSV_LINE::CASC_CSV_LINE() +{ + m_pParent = NULL; + m_nColumns = 0; +} + +CASC_CSV_LINE::~CASC_CSV_LINE() +{} + +bool CASC_CSV_LINE::SetLine(CASC_CSV * pParent, char * szCsvLine) +{ + char * szCsvColumn; + size_t nHdrColumns = 0; + size_t nColumns = 0; + + // Reset the number of column to zero + m_pParent = pParent; + m_nColumns = 0; + + // Parse each column + while(szCsvLine != NULL && nColumns < CSV_MAX_COLUMNS) + { + // Get current line and the next one + szCsvColumn = szCsvLine; + szCsvLine = NextColumn(szCsvLine); + + // Save the current line + Columns[nColumns].szValue = szCsvColumn; + Columns[nColumns].nLength = strlen(szCsvColumn); + nColumns++; + } + + // Columns overflow + if(nColumns >= CSV_MAX_COLUMNS) + { + assert(false); + return false; + } + + // If the parent has header lines, then the number of columns must match + // In the case of mismatched column count, ignore the line + nHdrColumns = pParent->GetHeaderColumns(); + if(nHdrColumns != 0 && nColumns != nHdrColumns) + return false; + + // All OK + m_nColumns = nColumns; + return true; +} + +const CASC_CSV_COLUMN & CASC_CSV_LINE::operator[](const char * szColumnName) const +{ + size_t nIndex; + + // The column must have a hash table + if(m_pParent != NULL) + { + nIndex = m_pParent->GetColumnIndex(szColumnName); + if(nIndex != CSV_INVALID_INDEX && nIndex < m_nColumns) + { + return Columns[nIndex]; + } + } + + return NullColumn; +} + +const CASC_CSV_COLUMN & CASC_CSV_LINE::operator[](size_t nIndex) const +{ + return (nIndex < m_nColumns) ? Columns[nIndex] : NullColumn; +} + +//----------------------------------------------------------------------------- +// Class for CSV object + +CASC_CSV::CASC_CSV(size_t nLinesMax, bool bHasHeader) +{ + // Initialize the class variables + memset(HashTable, 0xFF, sizeof(HashTable)); + m_szCsvFile = NULL; + m_szCsvPtr = NULL; + m_nCsvFile = 0; + m_nLines = 0; + m_bHasHeader = bHasHeader; + + // Nonzero number of lines means a finite CSV handler. The CSV will be loaded at once. + // Zero means that the user needs to call LoadNextLine() and then the line data + if(nLinesMax != 0) + { + m_pLines = new CASC_CSV_LINE[nLinesMax]; + m_nLinesMax = nLinesMax; + m_bHasAllLines = true; + } + else + { + m_pLines = new CASC_CSV_LINE[1]; + m_nLinesMax = 1; + m_bHasAllLines = false; + } +} + +CASC_CSV::~CASC_CSV() +{ + if(m_pLines != NULL) + delete[] m_pLines; + if(m_szCsvFile != NULL) + delete [] m_szCsvFile; + m_szCsvFile = NULL; +} + +int CASC_CSV::Load(const TCHAR * szFileName) +{ + DWORD cbFileData = 0; + int nError = ERROR_SUCCESS; + + m_szCsvFile = (char *)LoadFileToMemory(szFileName, &cbFileData); + if (m_szCsvFile != NULL) + { + // There is one extra byte reserved by LoadFileToMemory, so we can put zero there + m_szCsvFile[cbFileData] = 0; + m_szCsvPtr = m_szCsvFile; + m_nCsvFile = cbFileData; + + // Parse the data + nError = ParseCsvData() ? ERROR_SUCCESS : ERROR_BAD_FORMAT; + } + else + { + nError = GetLastError(); + } + + return nError; +} + +int CASC_CSV::Load(LPBYTE pbData, size_t cbData) +{ + int nError = ERROR_NOT_ENOUGH_MEMORY; + + m_szCsvFile = new char[cbData + 1]; + if (m_szCsvFile != NULL) + { + // Copy the entire data and terminate them with zero + memcpy(m_szCsvFile, pbData, cbData); + m_szCsvFile[cbData] = 0; + m_szCsvPtr = m_szCsvFile; + m_nCsvFile = cbData; + + // Parse the data + nError = ParseCsvData() ? ERROR_SUCCESS : ERROR_BAD_FORMAT; + } + + return nError; +} + +bool CASC_CSV::LoadNextLine() +{ + bool bResult = false; + + // Only endless CSV handler can do this + if(m_bHasAllLines == false) + { + // A few checks + assert(m_pLines != NULL); + assert(m_nLinesMax == 1); + + // Reset the current line and load it + bResult = LoadNextLine(m_pLines[0]); + m_nLines = (bResult) ? 1 : 0; + } + + return bResult; +} + +bool CASC_CSV::InitializeHashTable() +{ + // Create the hash table of HeaderText -> ColumnIndex + for(size_t i = 0; i < Header.GetColumnCount(); i++) + { + // Calculate the start slot and the current slot + size_t nStartIndex = HashToIndex(CalcHashValue(Header[i].szValue)); + size_t nHashIndex = nStartIndex; + + // Go as long as there is not a free space + while(HashTable[nHashIndex] != 0xFF) + { + nHashIndex = HashToIndex(nHashIndex + 1); + } + + // Set the slot + HashTable[nHashIndex] = (BYTE)i; + } + + return true; +} + +bool CASC_CSV::LoadNextLine(CASC_CSV_LINE & Line) +{ + // Overwatch ROOT's header begins with "#" + if(m_szCsvPtr == m_szCsvFile && m_szCsvPtr[0] == '#') + m_szCsvPtr++; + + // Parse the entire line + while(m_szCsvPtr != NULL && m_szCsvPtr[0] != 0) + { + char * szCsvLine = m_szCsvPtr; + + // Get the next line + m_szCsvPtr = NextLine(m_szCsvPtr); + + // Initialize the line + if (Line.SetLine(this, szCsvLine)) + return true; + } + + // End-of-file found + return false; +} + +bool CASC_CSV::ParseCsvData() +{ + // Sanity checks + assert(m_nLines == 0); + + // If we have header, then parse it and set the pointer to the next line + if(m_bHasHeader) + { + // Load the current line to the header + if (!LoadNextLine(Header)) + return false; + + // Initialize the hash table + if(!InitializeHashTable()) + return false; + } + + // Are we supposed to load all lines? + if(m_bHasAllLines) + { + // Parse each line + for(size_t i = 0; i < m_nLinesMax; i++) + { + if(!LoadNextLine(m_pLines[i])) + break; + m_nLines++; + } + } + + return true; +} + +const CASC_CSV_COLUMN & CASC_CSV::operator[](const char * szColumnName) const +{ + if (m_pLines == NULL || m_nLines == 0) + return NullColumn; + return m_pLines[0][GetColumnIndex(szColumnName)]; +} + +const CASC_CSV_LINE & CASC_CSV::operator[](size_t nIndex) const +{ + if (m_pLines == NULL || nIndex >= m_nLines) + return NullLine; + return m_pLines[nIndex]; +} + +size_t CASC_CSV::GetHeaderColumns() const +{ + return (m_bHasHeader) ? Header.GetColumnCount() : 0; +} + +size_t CASC_CSV::GetColumnIndex(const char * szColumnName) const +{ + if(m_bHasHeader) + { + // Calculate the start slot and the current slot + size_t nStartIndex = HashToIndex(CalcHashValue(szColumnName)); + size_t nHashIndex = nStartIndex; + size_t nIndex; + + // Go as long as there is not a free space + while((nIndex = HashTable[nHashIndex]) != 0xFF) + { + // Compare the string + if(!strcmp(Header[nIndex].szValue, szColumnName)) + return nIndex; + + // Move to the next column + nHashIndex = HashToIndex(nHashIndex + 1); + } + } + + return CSV_INVALID_INDEX; +} + diff --git a/dep/CascLib/src/common/Csv.h b/dep/CascLib/src/common/Csv.h new file mode 100644 index 00000000000..2138255e74e --- /dev/null +++ b/dep/CascLib/src/common/Csv.h @@ -0,0 +1,105 @@ +/*****************************************************************************/ +/* Csv.h Copyright (c) Ladislav Zezula 2019 */ +/*---------------------------------------------------------------------------*/ +/* Interface for the CSV handler class */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 24.05.19 1.00 Lad Created */ +/*****************************************************************************/ + +#ifndef __CSV_H__ +#define __CSV_H__ + +//----------------------------------------------------------------------------- +// Defines + +#define CSV_INVALID_INDEX ((size_t)(-1)) +#define CSV_ZERO ((size_t)(0)) // Use Csv[0][CSV_ZERO] instead of ambiguous Csv[0][0] +#define CSV_MAX_COLUMNS 0x10 +#define CSV_HASH_TABLE_SIZE 0x80 + +//----------------------------------------------------------------------------- +// Class for CSV line + +class CASC_CSV; + +struct CASC_CSV_COLUMN +{ + CASC_CSV_COLUMN() + { + szValue = NULL; + nLength = 0; + } + + const char * szValue; + size_t nLength; +}; + +class CASC_CSV_LINE +{ + public: + + CASC_CSV_LINE(); + ~CASC_CSV_LINE(); + + bool SetLine(CASC_CSV * pParent, char * szLine); + + const CASC_CSV_COLUMN & operator[](const char * szColumnName) const; + const CASC_CSV_COLUMN & operator[](size_t nIndex) const; + + size_t GetColumnCount() const + { + return m_nColumns; + } + + protected: + + friend class CASC_CSV; + + CASC_CSV * m_pParent; + CASC_CSV_COLUMN Columns[CSV_MAX_COLUMNS]; + size_t m_nColumns; +}; + +class CASC_CSV +{ + public: + + CASC_CSV(size_t nLinesMax, bool bHasHeader); + ~CASC_CSV(); + + int Load(LPBYTE pbData, size_t cbData); + int Load(const TCHAR * szFileName); + bool LoadNextLine(); + + const CASC_CSV_COLUMN & operator[](const char * szColumnName) const; + const CASC_CSV_LINE & operator[](size_t nIndex) const; + + size_t GetHeaderColumns() const; + size_t GetColumnIndex(const char * szColumnName) const; + + size_t GetLineCount() const + { + return m_nLines; + } + + protected: + + bool InitializeHashTable(); + bool LoadNextLine(CASC_CSV_LINE & Line); + bool ParseCsvData(); + + CASC_CSV_LINE * m_pLines; + CASC_CSV_LINE Header; + BYTE HashTable[CSV_HASH_TABLE_SIZE]; + char * m_szCsvFile; + char * m_szCsvPtr; + size_t m_nCsvFile; + size_t m_nLinesMax; + size_t m_nLines; + bool m_bHasHeader; + bool m_bHasAllLines; +}; + +#endif // __CSV_H__ diff --git a/dep/CascLib/src/common/Directory.cpp b/dep/CascLib/src/common/Directory.cpp index c4091095859..463881f3f9c 100644 --- a/dep/CascLib/src/common/Directory.cpp +++ b/dep/CascLib/src/common/Directory.cpp @@ -38,6 +38,20 @@ bool DirectoryExists(const TCHAR * szDirectory) return false; } +bool MakeDirectory(const TCHAR * szDirectory) +{ +#ifdef PLATFORM_WINDOWS + + BOOL bResult = CreateDirectory(szDirectory, NULL); + return (bResult) ? true : false; + +#else + + return (mkdir(szDirectory, 0755) == 0); + +#endif +} + int ScanIndexDirectory( const TCHAR * szIndexPath, INDEX_FILE_FOUND pfnOnFileFound, diff --git a/dep/CascLib/src/common/Directory.h b/dep/CascLib/src/common/Directory.h index ae97dca3d76..739f0612e54 100644 --- a/dep/CascLib/src/common/Directory.h +++ b/dep/CascLib/src/common/Directory.h @@ -18,6 +18,8 @@ typedef bool (*INDEX_FILE_FOUND)(const TCHAR * szFileName, PDWORD IndexArray, PD bool DirectoryExists(const TCHAR * szDirectory); +bool MakeDirectory(const TCHAR * szDirectory); + int ScanIndexDirectory( const TCHAR * szIndexPath, INDEX_FILE_FOUND pfnOnFileFound, diff --git a/dep/CascLib/src/common/DumpContext.cpp b/dep/CascLib/src/common/DumpContext.cpp deleted file mode 100644 index 8783ff201ba..00000000000 --- a/dep/CascLib/src/common/DumpContext.cpp +++ /dev/null @@ -1,153 +0,0 @@ -/*****************************************************************************/ -/* DumpContext.cpp Copyright (c) Ladislav Zezula 2015 */ -/*---------------------------------------------------------------------------*/ -/* Implementation of dump context */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 16.03.15 1.00 Lad Created */ -/*****************************************************************************/ - -#define __CASCLIB_SELF__ -#include "../CascLib.h" -#include "../CascCommon.h" - -//----------------------------------------------------------------------------- -// Local functions - -static TCHAR * FormatFileName(TCascStorage * hs, const TCHAR * szNameFormat) -{ - TCHAR * szFileName; - TCHAR * szSrc; - TCHAR * szTrg; - - // Create copy of the file name - szFileName = szSrc = szTrg = CascNewStr(szNameFormat, 0); - if(szFileName != NULL) - { - // Format the file name - while(szSrc[0] != 0) - { - if(szSrc[0] == _T('%')) - { - // Replace "%build%" with a build number - if(!_tcsncmp(szSrc, _T("%build%"), 7)) - { - szTrg += _stprintf(szTrg, _T("%u"), hs->dwBuildNumber); - szSrc += 7; - continue; - } - } - - // Just copy the character - *szTrg++ = *szSrc++; - } - - // Terminate the target file name - szTrg[0] = 0; - } - - return szFileName; -} - -//----------------------------------------------------------------------------- -// Public functions - -TDumpContext * CreateDumpContext(TCascStorage * hs, const TCHAR * szNameFormat) -{ - TDumpContext * dc; - TCHAR * szFileName; - - // Validate the storage handle - if(hs != NULL) - { - // Calculate the number of bytes needed for dump context - dc = CASC_ALLOC(TDumpContext, 1); - if(dc != NULL) - { - // Initialize the dump context - memset(dc, 0, sizeof(TDumpContext)); - - // Format the real file name - szFileName = FormatFileName(hs, szNameFormat); - if(szFileName != NULL) - { - // Create the file - dc->pStream = FileStream_CreateFile(szFileName, 0); - if(dc->pStream != NULL) - { - // Initialize buffers - dc->pbBufferBegin = - dc->pbBufferPtr = dc->DumpBuffer; - dc->pbBufferEnd = dc->DumpBuffer + CASC_DUMP_BUFFER_SIZE; - - // Success - CASC_FREE(szFileName); - return dc; - } - - // File create failed --> delete the file name - CASC_FREE(szFileName); - } - - // Free the dump context - CASC_FREE(dc); - } - } - - return NULL; -} - -int dump_print(TDumpContext * dc, const char * szFormat, ...) -{ - va_list argList; - char szBuffer[0x200]; - int nLength = 0; - - // Only if the dump context is valid - if(dc != NULL) - { - // Print the buffer using sprintf - va_start(argList, szFormat); - nLength = vsprintf(szBuffer, szFormat, argList); - assert(nLength < 0x200); - va_end(argList); - - // Copy the string. Replace "\n" with "\n\r" - for(int i = 0; i < nLength; i++) - { - // Flush the buffer, if needed - if((dc->pbBufferPtr + 2) >= dc->pbBufferEnd) - { - FileStream_Write(dc->pStream, NULL, dc->pbBufferBegin, (DWORD)(dc->pbBufferPtr - dc->pbBufferBegin)); - dc->pbBufferPtr = dc->pbBufferBegin; - } - - // Copy the char - if(szBuffer[i] == 0x0A) - *dc->pbBufferPtr++ = 0x0D; - *dc->pbBufferPtr++ = szBuffer[i]; - } - } - - return nLength; -} - -int dump_close(TDumpContext * dc) -{ - // Only if the dump context is valid - if(dc != NULL) - { - // Flush the dump context if there are some data - if(dc->pbBufferPtr > dc->pbBufferBegin) - FileStream_Write(dc->pStream, NULL, dc->pbBufferBegin, (DWORD)(dc->pbBufferPtr - dc->pbBufferBegin)); - dc->pbBufferPtr = dc->pbBufferBegin; - - // Free the file stream and the entire context - if(dc->pStream != NULL) - FileStream_Close(dc->pStream); - CASC_FREE(dc); - } - - return 0; -} diff --git a/dep/CascLib/src/common/DumpContext.h b/dep/CascLib/src/common/DumpContext.h deleted file mode 100644 index 6f725f5b942..00000000000 --- a/dep/CascLib/src/common/DumpContext.h +++ /dev/null @@ -1,38 +0,0 @@ -/*****************************************************************************/ -/* DumpContext.h Copyright (c) Ladislav Zezula 2015 */ -/*---------------------------------------------------------------------------*/ -/* Interface for TDumpContext */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 16.03.15 1.00 Lad Created */ -/*****************************************************************************/ - -#ifndef __DUMP_CONTEXT_H__ -#define __DUMP_CONTEXT_H__ - -//----------------------------------------------------------------------------- -// Defines - -// Size of the buffer for the dump context -#define CASC_DUMP_BUFFER_SIZE 0x10000 - -// Structure for dump context -struct TDumpContext -{ - TFileStream * pStream; // Pointer to the open stream - LPBYTE pbBufferBegin; // Begin of the dump buffer - LPBYTE pbBufferPtr; // Current dump buffer pointer - LPBYTE pbBufferEnd; // End of the dump buffer - - BYTE DumpBuffer[CASC_DUMP_BUFFER_SIZE]; // Dump buffer -}; - -//----------------------------------------------------------------------------- -// Dump context functions - -TDumpContext * CreateDumpContext(struct _TCascStorage * hs, const TCHAR * szNameFormat); -int dump_print(TDumpContext * dc, const char * szFormat, ...); -int dump_close(TDumpContext * dc); - -#endif // __DUMP_CONTEXT_H__ diff --git a/dep/CascLib/src/common/DynamicArray.cpp b/dep/CascLib/src/common/DynamicArray.cpp deleted file mode 100644 index e744fbe3912..00000000000 --- a/dep/CascLib/src/common/DynamicArray.cpp +++ /dev/null @@ -1,101 +0,0 @@ -/*****************************************************************************/ -/* DynamicArray.cpp Copyright (c) Ladislav Zezula 2015 */ -/*---------------------------------------------------------------------------*/ -/* Description: */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 30.10.15 1.00 Lad The first version of DynamicArray.cpp */ -/*****************************************************************************/ - -#define __CASCLIB_SELF__ -#include "../CascLib.h" -#include "../CascCommon.h" - -//----------------------------------------------------------------------------- -// Local functions - -static bool EnlargeArray(PDYNAMIC_ARRAY pArray, size_t NewItemCount) -{ - char * NewItemArray; - size_t ItemCountMax; - - // We expect the array to be already allocated - assert(pArray->ItemArray != NULL); - assert(pArray->ItemCountMax != 0); - - // Shall we enlarge the table? - if(NewItemCount > pArray->ItemCountMax) - { - // Calculate new table size - ItemCountMax = pArray->ItemCountMax; - while(ItemCountMax < NewItemCount) - ItemCountMax = ItemCountMax << 1; - - // Allocate new table - NewItemArray = CASC_REALLOC(char, pArray->ItemArray, pArray->ItemSize * ItemCountMax); - if(NewItemArray == NULL) - return false; - - // Set the new table size - pArray->ItemCountMax = ItemCountMax; - pArray->ItemArray = NewItemArray; - } - - return true; -} - -//----------------------------------------------------------------------------- -// Public functions - -int Array_Create_(PDYNAMIC_ARRAY pArray, size_t ItemSize, size_t ItemCountMax) -{ - pArray->ItemArray = CASC_ALLOC(char, (ItemSize * ItemCountMax)); - if(pArray->ItemArray == NULL) - return ERROR_NOT_ENOUGH_MEMORY; - - pArray->ItemCountMax = ItemCountMax; - pArray->ItemCount = 0; - pArray->ItemSize = ItemSize; - return ERROR_SUCCESS; -} - -void * Array_Insert(PDYNAMIC_ARRAY pArray, const void * NewItems, size_t NewItemCount) -{ - char * NewItemPtr; - - // Try to enlarge the buffer, if needed - if(!EnlargeArray(pArray, pArray->ItemCount + NewItemCount)) - return NULL; - NewItemPtr = pArray->ItemArray + (pArray->ItemCount * pArray->ItemSize); - - // Copy the old item(s), if any - if(NewItems != NULL) - memcpy(NewItemPtr, NewItems, (NewItemCount * pArray->ItemSize)); - - // Increment the size of the array - pArray->ItemCount += NewItemCount; - return NewItemPtr; -} - -void * Array_ItemAt(PDYNAMIC_ARRAY pArray, size_t ItemIndex) -{ - assert(ItemIndex < pArray->ItemCount); - return pArray->ItemArray + (ItemIndex * pArray->ItemSize); -} - -size_t Array_IndexOf(PDYNAMIC_ARRAY pArray, const void * ArrayPtr) -{ - char * ArrayItem = (char *)ArrayPtr; - - assert(pArray->ItemArray <= ArrayItem && ArrayItem <= pArray->ItemArray + (pArray->ItemCount * pArray->ItemSize)); - return ((ArrayItem - pArray->ItemArray) / pArray->ItemSize); -} - -void Array_Free(PDYNAMIC_ARRAY pArray) -{ - if(pArray != NULL && pArray->ItemArray != NULL) - { - CASC_FREE(pArray->ItemArray); - } -} diff --git a/dep/CascLib/src/common/DynamicArray.h b/dep/CascLib/src/common/DynamicArray.h deleted file mode 100644 index 11cefacdcc4..00000000000 --- a/dep/CascLib/src/common/DynamicArray.h +++ /dev/null @@ -1,37 +0,0 @@ -/*****************************************************************************/ -/* DynamicArray.h Copyright (c) Ladislav Zezula 2015 */ -/*---------------------------------------------------------------------------*/ -/* Common array implementation */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 30.10.15 1.00 Lad The first version of DynamicArray.h */ -/*****************************************************************************/ - -#ifndef __DYNAMIC_ARRAY_H__ -#define __DYNAMIC_ARRAY_H__ - -//----------------------------------------------------------------------------- -// Structures - -typedef struct _DYNAMIC_ARRAY -{ - char * ItemArray; // Pointer to items - size_t ItemCountMax; // Current number of items - size_t ItemCount; // Total size of the buffer - size_t ItemSize; // Size of the single item - -} DYNAMIC_ARRAY, *PDYNAMIC_ARRAY; - -//----------------------------------------------------------------------------- -// Functions for managing the array - -int Array_Create_(PDYNAMIC_ARRAY pArray, size_t ItemSize, size_t ItemCountMax); -void * Array_Insert(PDYNAMIC_ARRAY pArray, const void * NewItems, size_t NewItemCount); -void * Array_ItemAt(PDYNAMIC_ARRAY pArray, size_t ItemIndex); -size_t Array_IndexOf(PDYNAMIC_ARRAY pArray, const void * ArrayPtr); -void Array_Free(PDYNAMIC_ARRAY pArray); - -#define Array_Create(pArray, type, ItemCountMax) Array_Create_(pArray, sizeof(type), ItemCountMax) - -#endif // __DYNAMIC_ARRAY__ diff --git a/dep/CascLib/src/common/FileStream.cpp b/dep/CascLib/src/common/FileStream.cpp index 22d7fceb4c2..845e793a6fa 100644 --- a/dep/CascLib/src/common/FileStream.cpp +++ b/dep/CascLib/src/common/FileStream.cpp @@ -219,10 +219,24 @@ static bool BaseFile_Read( #endif // Increment the current file position by number of bytes read - // If the number of bytes read doesn't match to required amount, return false pStream->Base.File.FilePos = ByteOffset + dwBytesRead; - if(dwBytesRead != dwBytesToRead) - SetLastError(ERROR_HANDLE_EOF); + + // If the number of bytes read doesn't match to required amount, return false + // However, Blizzard's CASC handlers read encoded data so that if less than expected + // was read, then they fill the rest with zeros + if(dwBytesRead < dwBytesToRead) + { + if(pStream->dwFlags & STREAM_FLAG_FILL_MISSING) + { + memset((LPBYTE)pvBuffer + dwBytesRead, 0, (dwBytesToRead - dwBytesRead)); + dwBytesRead = dwBytesToRead; + } + else + { + SetLastError(ERROR_HANDLE_EOF); + } + } + return (dwBytesRead == dwBytesToRead); } @@ -597,22 +611,26 @@ static bool BaseHttp_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD { #ifdef PLATFORM_WINDOWS + INTERNET_PORT ServerPort = INTERNET_DEFAULT_HTTP_PORT; HINTERNET hRequest; DWORD dwTemp = 0; bool bFileAvailable = false; int nError = ERROR_SUCCESS; - // Keep compiler happy - dwStreamFlags = dwStreamFlags; + // Check alternate ports + if(dwStreamFlags & STREAM_FLAG_USE_PORT_1119) + { + ServerPort = 1119; + } - // Don't connect to the internet + // Don't download if we are not connected to the internet if(!InternetGetConnectedState(&dwTemp, 0)) nError = GetLastError(); // Initiate the connection to the internet if(nError == ERROR_SUCCESS) { - pStream->Base.Http.hInternet = InternetOpen(_T("CascLib HTTP archive reader"), + pStream->Base.Http.hInternet = InternetOpen(_T("agent/2.17.2.6700"), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, @@ -631,7 +649,7 @@ static bool BaseHttp_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD szFileName = BaseHttp_ExtractServerName(szFileName, szServerName); pStream->Base.Http.hConnect = InternetConnect(pStream->Base.Http.hInternet, szServerName, - INTERNET_DEFAULT_HTTP_PORT, + ServerPort, NULL, NULL, INTERNET_SERVICE_HTTP, @@ -651,26 +669,41 @@ static bool BaseHttp_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD if(HttpSendRequest(hRequest, NULL, 0, NULL, 0)) { ULONGLONG FileTime = 0; + LPTSTR szEndPtr; + TCHAR szStatusCode[0x10]; + DWORD dwStatusCode = 404; DWORD dwFileSize = 0; - DWORD dwDataSize; + DWORD dwDataSize = sizeof(szStatusCode); DWORD dwIndex = 0; - // Check if the archive has Last Modified field - dwDataSize = sizeof(ULONGLONG); - if(HttpQueryInfo(hRequest, HTTP_QUERY_LAST_MODIFIED | HTTP_QUERY_FLAG_SYSTEMTIME, &FileTime, &dwDataSize, &dwIndex)) - pStream->Base.Http.FileTime = FileTime; + // Check whether the file exists + if (HttpQueryInfo(hRequest, HTTP_QUERY_STATUS_CODE, szStatusCode, &dwDataSize, &dwIndex)) + dwStatusCode = _tcstoul(szStatusCode, &szEndPtr, 10); - // Verify if the server supports random access - dwDataSize = sizeof(DWORD); - if(HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &dwFileSize, &dwDataSize, &dwIndex)) + // Copare the statuc code + if (dwStatusCode == 200) { - if(dwFileSize != 0) + // Check if the archive has Last Modified field + dwDataSize = sizeof(ULONGLONG); + if (HttpQueryInfo(hRequest, HTTP_QUERY_LAST_MODIFIED | HTTP_QUERY_FLAG_SYSTEMTIME, &FileTime, &dwDataSize, &dwIndex)) + pStream->Base.Http.FileTime = FileTime; + + // Verify if the server supports random access + dwDataSize = sizeof(DWORD); + if (HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &dwFileSize, &dwDataSize, &dwIndex)) { - pStream->Base.Http.FileSize = dwFileSize; - pStream->Base.Http.FilePos = 0; - bFileAvailable = true; + if (dwFileSize != 0) + { + pStream->Base.Http.FileSize = dwFileSize; + pStream->Base.Http.FilePos = 0; + bFileAvailable = true; + } } } + else + { + nError = ERROR_FILE_NOT_FOUND; + } } InternetCloseHandle(hRequest); } @@ -681,6 +714,7 @@ static bool BaseHttp_Open(TFileStream * pStream, const TCHAR * szFileName, DWORD if(bFileAvailable == false) { pStream->BaseClose(pStream); + SetLastError(nError); return false; } @@ -723,7 +757,7 @@ static bool BaseHttp_Read( { // Add range request to the HTTP headers // http://www.clevercomponents.com/articles/article015/resuming.asp - _stprintf(szRangeRequest, _T("Range: bytes=%u-%u"), (unsigned int)dwStartOffset, (unsigned int)dwEndOffset); + CascStrPrintf(szRangeRequest, _countof(szRangeRequest), _T("Range: bytes=%u-%u"), (unsigned int)dwStartOffset, (unsigned int)dwEndOffset); HttpAddRequestHeaders(hRequest, szRangeRequest, 0xFFFFFFFF, HTTP_ADDREQ_FLAG_ADD_IF_NEW); // Send the request to the server @@ -963,9 +997,7 @@ static bool BlockStream_GetPos(TFileStream * pStream, ULONGLONG * pByteOffset) static void BlockStream_Close(TBlockStream * pStream) { // Free the data map, if any - if(pStream->FileBitmap != NULL) - CASC_FREE(pStream->FileBitmap); - pStream->FileBitmap = NULL; + CASC_FREE(pStream->FileBitmap); // Call the base class for closing the stream pStream->BaseClose(pStream); @@ -1026,7 +1058,7 @@ static TFileStream * AllocateFileStream( if(pStream != NULL) { // Zero the entire structure - memset(pStream, 0, StreamSize); + memset(pStream, 0, StreamSize + FileNameSize + sizeof(TCHAR)); pStream->pMaster = pMaster; pStream->dwFlags = dwStreamFlags; @@ -1344,7 +1376,10 @@ static TFileStream * FlatStream_Open(const TCHAR * szFileName, DWORD dwStreamFla // Create new empty stream pStream = (TBlockStream *)AllocateFileStream(szFileName, sizeof(TBlockStream), dwStreamFlags); if(pStream == NULL) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); return NULL; + } // Do we have a master stream? if(pStream->pMaster != NULL) @@ -1661,7 +1696,7 @@ static void PartStream_Close(TBlockStream * pStream) // Make sure that the header is properly BSWAPed BSWAP_ARRAY32_UNSIGNED(&PartHeader, sizeof(PART_FILE_HEADER)); - sprintf(PartHeader.GameBuildNumber, "%u", (unsigned int)pStream->BuildNumber); + CascStrPrintf(PartHeader.GameBuildNumber, _countof(PartHeader.GameBuildNumber), "%u", (unsigned int)pStream->BuildNumber); // Write the part header pStream->BaseWrite(pStream, &ByteOffset, &PartHeader, sizeof(PART_FILE_HEADER)); @@ -1755,7 +1790,6 @@ static bool PartStream_CreateMirror(TBlockStream * pStream) return true; } - static TFileStream * PartStream_Open(const TCHAR * szFileName, DWORD dwStreamFlags) { TBlockStream * pStream; @@ -2193,9 +2227,7 @@ static void Block4Stream_Close(TBlockStream * pStream) } // Free the data map, if any - if(pStream->FileBitmap != NULL) - CASC_FREE(pStream->FileBitmap); - pStream->FileBitmap = NULL; + CASC_FREE(pStream->FileBitmap); // Do not call the BaseClose function, // we closed all handles already @@ -2246,7 +2278,7 @@ static TFileStream * Block4Stream_Open(const TCHAR * szFileName, DWORD dwStreamF for(int nSuffix = 0; nSuffix < 30; nSuffix++) { // Open the n-th file - _stprintf(szNameBuff, _T("%s.%u"), pStream->szFileName, nSuffix); + CascStrPrintf(szNameBuff, (nNameLength + 4), _T("%s.%u"), pStream->szFileName, nSuffix); if(!pStream->BaseOpen(pStream, szNameBuff, dwBaseFlags)) break; @@ -2368,7 +2400,6 @@ TFileStream * FileStream_CreateFile( // File create failed, delete the stream CASC_FREE(pStream); - pStream = NULL; } // Return the stream diff --git a/dep/CascLib/src/common/FileStream.h b/dep/CascLib/src/common/FileStream.h index 1c72619e393..a2b0c5b9f7d 100644 --- a/dep/CascLib/src/common/FileStream.h +++ b/dep/CascLib/src/common/FileStream.h @@ -28,6 +28,8 @@ #define STREAM_FLAG_READ_ONLY 0x00000100 // Stream is read only #define STREAM_FLAG_WRITE_SHARE 0x00000200 // Allow write sharing when open for write #define STREAM_FLAG_USE_BITMAP 0x00000400 // If the file has a file bitmap, load it and use it +#define STREAM_FLAG_FILL_MISSING 0x00000800 // If less than expected was read from the file, fill the missing part with zeros +#define STREAM_FLAG_USE_PORT_1119 0x00001000 // For HTTP streams, use port 1119 #define STREAM_OPTIONS_MASK 0x0000FF00 // Mask for stream options #define STREAM_PROVIDERS_MASK 0x000000FF // Mask to get stream providers @@ -101,6 +103,12 @@ typedef void (*BLOCK_SAVEMAP)( struct TFileStream * pStream // Pointer to a block-oriented stream ); +typedef void (WINAPI * STREAM_DOWNLOAD_CALLBACK)( + void * pvUserData, + ULONGLONG ByteOffset, + DWORD dwTotalBytes + ); + //----------------------------------------------------------------------------- // Local structures - partial file structure and bitmap footer diff --git a/dep/CascLib/src/common/FileTree.cpp b/dep/CascLib/src/common/FileTree.cpp new file mode 100644 index 00000000000..f753c25f263 --- /dev/null +++ b/dep/CascLib/src/common/FileTree.cpp @@ -0,0 +1,684 @@ +/*****************************************************************************/ +/* FileTree.cpp Copyright (c) Ladislav Zezula 2018 */ +/*---------------------------------------------------------------------------*/ +/* Common implementation of a file tree object for various ROOt file formats */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 29.05.18 1.00 Lad The first version of FileTree.cpp */ +/*****************************************************************************/ + +#define __CASCLIB_SELF__ +#include "../CascLib.h" +#include "../CascCommon.h" + +//----------------------------------------------------------------------------- +// Local defines + +#define START_ITEM_COUNT 0x4000 + +inline DWORD GET_NODE_INT32(void * node, size_t offset) +{ + PDWORD PtrValue = (PDWORD)((LPBYTE)node + offset); + + return PtrValue[0]; +} + +inline void SET_NODE_INT32(void * node, size_t offset, DWORD value) +{ + PDWORD PtrValue = (PDWORD)((LPBYTE)node + offset); + + PtrValue[0] = value; +} +/* +static bool CompareFileNode(void * pvObject, void * pvUserData) +{ + PCASC_COMPARE_CONTEXT pCtx = (PCASC_COMPARE_CONTEXT)pvUserData; + PCASC_FILE_TREE pFileTree = (PCASC_FILE_TREE)pCtx->pThis; + PCASC_FILE_NODE pFileNode = (PCASC_FILE_NODE)pvObject; + char szFullPath[MAX_PATH]; + + // First of all, the name hash must match + if(pFileNode->FileNameHash == pCtx->FileNameHash) + { + // Then also compare the full path name + pFileTree->PathAt(szFullPath, _countof(szFullPath), pFileNode); + if(!_stricmp(szFullPath, pCtx->szFileName)) + { + return true; + } + } + + return false; +} +*/ +//----------------------------------------------------------------------------- +// Protected functions + +// Inserts a new file node to the file tree. +// If the pointer to file node array changes, the function also rebuilds all maps +PCASC_FILE_NODE CASC_FILE_TREE::InsertNew(PCASC_CKEY_ENTRY pCKeyEntry) +{ + PCASC_FILE_NODE pFileNode; + + // Create a brand new node + pFileNode = InsertNew(); + if(pFileNode != NULL) + { + // Initialize the file node's CKeyEntry + pFileNode->pCKeyEntry = pCKeyEntry; + + // Don't insert the node into any of the arrays here. + // That is the caller's responsibility + } + + return pFileNode; +} + +PCASC_FILE_NODE CASC_FILE_TREE::InsertNew() +{ + PCASC_FILE_NODE pFileNode; + void * SaveItemArray = NodeTable.ItemArray(); // We need to save the array pointers. If it changes, we must rebuild both maps + + // Create a brand new node + pFileNode = (PCASC_FILE_NODE)NodeTable.Insert(1); + if(pFileNode != NULL) + { + // Initialize the file node + pFileNode->FileNameHash = 0; + pFileNode->pCKeyEntry = NULL; + pFileNode->Parent = 0; + pFileNode->NameIndex = 0; + pFileNode->NameLength = 0; + pFileNode->Flags = 0; + + // We need to supply a file data id for the new entry, otherwise the rebuilding function + // will use the uninitialized one + SetExtras(pFileNode, CASC_INVALID_ID, CASC_INVALID_ID, CASC_INVALID_ID); + + // If the array pointer changed or we are close to the size of the array, we need to rebuild the maps + if(NodeTable.ItemArray() != SaveItemArray || (NodeTable.ItemCount() * 3 / 2) > NameMap.HashTableSize()) + { + // Rebuild both maps. Note that rebuilding also inserts all items to the maps, so no need to insert them here + if(!RebuildNameMaps()) + { + pFileNode = NULL; + assert(false); + } + } + } + + return pFileNode; +} + +// Insert the node to the map of FileNameHash -> CASC_FILE_NODE +bool CASC_FILE_TREE::InsertToHashTable(PCASC_FILE_NODE pFileNode) +{ + bool bResult = false; + + // Insert the file node to the table + if(pFileNode->FileNameHash != 0) + bResult = NameMap.InsertObject(pFileNode, &pFileNode->FileNameHash); + return bResult; +} + +// Inserts the file node to the array of file data ids +bool CASC_FILE_TREE::InsertToIdTable(PCASC_FILE_NODE pFileNode) +{ + PCASC_FILE_NODE * RefElement; + DWORD FileDataId = CASC_INVALID_ID; + + if(FileDataIds.IsInitialized()) + { + // Retrieve the file data id + GetExtras(pFileNode, &FileDataId, NULL, NULL); + if(FileDataId != CASC_INVALID_ID) + { + // Sanity check + assert(FileDataId < 0x10000000); + + // Insert the element to the array + RefElement = (PCASC_FILE_NODE *)FileDataIds.InsertAt(FileDataId); + if(RefElement != NULL) + { + RefElement[0] = pFileNode; + return true; + } + } + } + + return false; +} + +bool CASC_FILE_TREE::SetNodePlainName(PCASC_FILE_NODE pFileNode, const char * szPlainName, const char * szPlainNameEnd) +{ + char * szNodeName; + size_t nLength = (szPlainNameEnd - szPlainName); + + // Insert all chars to the name array + szNodeName = (char *)NameTable.Insert(nLength); + if(szNodeName != NULL) + { + // Copy the plain name to the node. Do not include the string terminator + memcpy(szNodeName, szPlainName, nLength); + + // Supply the file name to the file node + pFileNode->NameIndex = (DWORD)NameTable.IndexOf(szNodeName); + pFileNode->NameLength = (USHORT)nLength; + return true; + } + + return false; +} + +bool CASC_FILE_TREE::SetKeyLength(DWORD aKeyLength) +{ + if(aKeyLength > MD5_HASH_SIZE) + return false; + KeyLength = aKeyLength; + return true; +} + +DWORD CASC_FILE_TREE::GetNextFileDataId() +{ + if(FileDataIds.IsInitialized()) + return (DWORD)(FileDataIds.ItemCount() + 1); + return CASC_INVALID_ID; +} + +bool CASC_FILE_TREE::RebuildNameMaps() +{ + PCASC_FILE_NODE pFileNode; + size_t nMaxItems = NodeTable.ItemCountMax(); + + // Free the map of "FullName -> CASC_FILE_NODE" + NameMap.Free(); + + // Create new map map "FullName -> CASC_FILE_NODE" + if(NameMap.Create(nMaxItems, sizeof(ULONGLONG), FIELD_OFFSET(CASC_FILE_NODE, FileNameHash)) != ERROR_SUCCESS) + return false; + + // Reset the entire array, but keep the buffer allocated + FileDataIds.Reset(); + + // Parse all items and insert them to the map + for(size_t i = 0; i < NodeTable.ItemCount(); i++) + { + // Retrieve the n-th object + pFileNode = (PCASC_FILE_NODE)NodeTable.ItemAt(i); + if(pFileNode != NULL) + { + // Insert it to the map "FileNameHash -> CASC_FILE_NODE" + if(pFileNode->FileNameHash != 0) + InsertToHashTable(pFileNode); + + // Insert it to the array "FileDataId -> CASC_FILE_NODE" + if(FileDataIds.IsInitialized()) + InsertToIdTable(pFileNode); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Public functions + +int CASC_FILE_TREE::Create(DWORD Flags) +{ + PCASC_FILE_NODE pRootNode; + size_t FileNodeSize = FIELD_OFFSET(CASC_FILE_NODE, ExtraValues); + int nError; + + // Initialize the file tree + memset(this, 0, sizeof(CASC_FILE_TREE)); + KeyLength = MD5_HASH_SIZE; + + // Shall we use the data ID in the tree node? + if(Flags & FTREE_FLAG_USE_DATA_ID) + { + // Set the offset of the file data id in the entry + FileDataIdOffset = FileNodeSize; + FileNodeSize += sizeof(DWORD); + + // Create the array for FileDataId -> CASC_FILE_NODE + nError = FileDataIds.Create<PCASC_FILE_NODE>(START_ITEM_COUNT); + if(nError != ERROR_SUCCESS) + return nError; + } + + // Shall we use the locale ID in the tree node? + if(Flags & FTREE_FLAG_USE_LOCALE_FLAGS) + { + LocaleFlagsOffset = FileNodeSize; + FileNodeSize += sizeof(DWORD); + } + + if(Flags & FTREE_FLAG_USE_CONTENT_FLAGS) + { + ContentFlagsOffset = FileNodeSize; + FileNodeSize += sizeof(DWORD); + } + + // Align the file node size to 8 bytes + FileNodeSize = ALIGN_TO_SIZE(FileNodeSize, 8); + + // Initialize the dynamic array + nError = NodeTable.Create(FileNodeSize, START_ITEM_COUNT); + if(nError == ERROR_SUCCESS) + { + // Create the dynamic array that will hold the node names + nError = NameTable.Create<char>(START_ITEM_COUNT); + if(nError == ERROR_SUCCESS) + { + // Insert the first "root" node, without name + pRootNode = (PCASC_FILE_NODE)NodeTable.Insert(1); + if(pRootNode != NULL) + { + // Initialize the node + memset(pRootNode, 0, NodeTable.ItemSize()); + pRootNode->Parent = CASC_INVALID_INDEX; + pRootNode->NameIndex = CASC_INVALID_INDEX; + pRootNode->Flags = CFN_FLAG_FOLDER; + SetExtras(pRootNode, CASC_INVALID_ID, CASC_INVALID_ID, CASC_INVALID_ID); + } + } + } + + // Create both maps + if(!RebuildNameMaps()) + nError = ERROR_NOT_ENOUGH_MEMORY; + return nError; +} + +void CASC_FILE_TREE::Free() +{ + // Free both arrays + NodeTable.Free(); + NameTable.Free(); + FileDataIds.Free(); + + // Free the name map + NameMap.Free(); + + // Zero the object + memset(this, 0, sizeof(CASC_FILE_TREE)); +} + +PCASC_FILE_NODE CASC_FILE_TREE::InsertByName(PCASC_CKEY_ENTRY pCKeyEntry, const char * szFileName, DWORD FileDataId, DWORD LocaleFlags, DWORD ContentFlags) +{ + CASC_COMPARE_CONTEXT CmpCtx; + PCASC_FILE_NODE pFileNode; + + // Sanity checks + assert(szFileName != NULL && szFileName[0] != 0); + assert(pCKeyEntry != NULL); + + // Calculate the file name hash + CmpCtx.FileNameHash = CalcFileNameHash(szFileName); +// CmpCtx.szFileName = szFileName; +// CmpCtx.pThis = this; + + // Do nothing if the file name is there already. +// pFileNode = (PCASC_FILE_NODE)NameMap.FindObjectEx(CompareFileNode, &CmpCtx); + pFileNode = (PCASC_FILE_NODE)NameMap.FindObject(&CmpCtx.FileNameHash); + if(pFileNode == NULL) + { + // Insert new item + pFileNode = InsertNew(pCKeyEntry); + if(pFileNode != NULL) + { + // Supply the name hash + pFileNode->FileNameHash = CmpCtx.FileNameHash; + + // Set the file data id and the extra values + SetExtras(pFileNode, FileDataId, LocaleFlags, ContentFlags); + + // Insert the file node to the hash map + InsertToHashTable(pFileNode); + + // Also make sure that it's in the file data id table, if the table is initialized + InsertToIdTable(pFileNode); + + // Set the file name of the new file node. This also increments the number of references + SetNodeFileName(pFileNode, szFileName); + + // If we created a new node, we need to increment the reference count + assert(pCKeyEntry->RefCount != 0xFFFF); + pCKeyEntry->RefCount++; + } + } + + return pFileNode; +} + +PCASC_FILE_NODE CASC_FILE_TREE::InsertByHash(PCASC_CKEY_ENTRY pCKeyEntry, ULONGLONG FileNameHash, DWORD FileDataId, DWORD LocaleFlags, DWORD ContentFlags) +{ + PCASC_FILE_NODE pFileNode; + + // Sanity checks + assert(FileDataIds.IsInitialized()); + assert(FileDataId != CASC_INVALID_ID); + assert(FileNameHash != 0); + assert(pCKeyEntry != NULL); + + // Insert the node to the tree by file data id + pFileNode = InsertById(pCKeyEntry, FileDataId, LocaleFlags, ContentFlags); + if(pFileNode != NULL) + { + // Supply the name hash + pFileNode->FileNameHash = FileNameHash; + + // Insert the file node to the hash map + InsertToHashTable(pFileNode); + } + + return pFileNode; +} + +PCASC_FILE_NODE CASC_FILE_TREE::InsertById(PCASC_CKEY_ENTRY pCKeyEntry, DWORD FileDataId, DWORD LocaleFlags, DWORD ContentFlags) +{ + PCASC_FILE_NODE pFileNode; + + // Sanity checks + assert(FileDataIds.IsInitialized()); + assert(FileDataId != CASC_INVALID_ID); + assert(pCKeyEntry != NULL); + + // Check whether the file data id exists in the array of file data ids + if((pFileNode = FindById(FileDataId)) == NULL) + { + // Insert the new file node + pFileNode = InsertNew(pCKeyEntry); + if(pFileNode != NULL) + { + // Set the file data id and the extra values + SetExtras(pFileNode, FileDataId, LocaleFlags, ContentFlags); + + // Insert the file node to the FileDataId array + InsertToIdTable(pFileNode); + + // Increment the number of references + pCKeyEntry->RefCount++; + } + } + + // Return the new or old node + return pFileNode; +} + +PCASC_FILE_NODE CASC_FILE_TREE::ItemAt(size_t nItemIndex) +{ + return (PCASC_FILE_NODE)NodeTable.ItemAt(nItemIndex); +} + +PCASC_FILE_NODE CASC_FILE_TREE::PathAt(char * szBuffer, size_t cchBuffer, size_t nItemIndex) +{ + PCASC_FILE_NODE pFileNode = NULL; + + // If we have FileDataId, then we need to enumerate the files by FileDataId + if(FileDataIds.IsInitialized()) + pFileNode = *(PCASC_FILE_NODE *)FileDataIds.ItemAt(nItemIndex); + else + pFileNode = (PCASC_FILE_NODE)NodeTable.ItemAt(nItemIndex); + + // Construct the entire path + PathAt(szBuffer, cchBuffer, pFileNode); + return pFileNode; +} + +size_t CASC_FILE_TREE::PathAt(char * szBuffer, size_t cchBuffer, PCASC_FILE_NODE pFileNode) +{ + PCASC_FILE_NODE pParentNode; + const char * szNamePtr; + char * szSaveBuffer = szBuffer; + char * szBufferEnd = szBuffer + cchBuffer - 1; + + if(pFileNode != NULL && pFileNode->Parent != CASC_INVALID_INDEX) + { + // Copy all parents + pParentNode = (PCASC_FILE_NODE)NodeTable.ItemAt(pFileNode->Parent); + if(pParentNode != NULL) + { + // Query the parent and move the buffer + szBuffer = szBuffer + PathAt(szBuffer, cchBuffer, pParentNode); + } + + // Retrieve the node name + szNamePtr = (const char *)NameTable.ItemAt(pFileNode->NameIndex); + + // Check whether we have enough space + if((szBuffer + pFileNode->NameLength) < szBufferEnd) + { + // Copy the path part + memcpy(szBuffer, szNamePtr, pFileNode->NameLength); + szBuffer += pFileNode->NameLength; + + // Append backslash + if((pFileNode->Flags & CFN_FLAG_FOLDER) && ((szBuffer + 1) < szBufferEnd)) + { + *szBuffer++ = (pFileNode->Flags & CFN_FLAG_MOUNT_POINT) ? ':' : '\\'; + } + } + } + + // Terminate buffer with zero + szBuffer[0] = 0; + + // Return length of the copied string + return (szBuffer - szSaveBuffer); +} + +PCASC_FILE_NODE CASC_FILE_TREE::Find(const char * szFullPath, DWORD FileDataId, PCASC_FIND_DATA pFindData) +{ + PCASC_FILE_NODE pFileNode = NULL; + ULONGLONG FileNameHash; + + // Can we search by FileDataId? + if(FileDataIds.IsInitialized() && (FileDataId != CASC_INVALID_ID || IsFileDataIdName(szFullPath, FileDataId))) + { + pFileNode = FindById(FileDataId); + } + else + { + if(szFullPath != NULL && szFullPath[0] != 0) + { + FileNameHash = CalcFileNameHash(szFullPath); + pFileNode = (PCASC_FILE_NODE)NameMap.FindObject(&FileNameHash); + } + } + + // Did we find anything? + if(pFileNode != NULL && pFindData != NULL) + { + GetExtras(pFileNode, &pFindData->dwFileDataId, &pFindData->dwLocaleFlags, &pFindData->dwContentFlags); + pFindData->bCanOpenByName = (pFileNode->FileNameHash != 0); + pFindData->bCanOpenByDataId = (FileDataIdOffset != 0); + } + + return pFileNode; +} + +PCASC_FILE_NODE CASC_FILE_TREE::Find(PCASC_CKEY_ENTRY pCKeyEntry) +{ + PCASC_FILE_NODE pFileNode; + + for(size_t i = 0; i < NodeTable.ItemCount(); i++) + { + pFileNode = (PCASC_FILE_NODE)NodeTable.ItemAt(i); + if((pFileNode->Flags & (CFN_FLAG_FOLDER | CFN_FLAG_MOUNT_POINT)) == 0) + { + if(pFileNode->pCKeyEntry == pCKeyEntry) + return pFileNode; + } + } + + return NULL; +} + +PCASC_FILE_NODE CASC_FILE_TREE::Find(ULONGLONG FileNameHash) +{ + return (PCASC_FILE_NODE)NameMap.FindObject(&FileNameHash); +} + +PCASC_FILE_NODE CASC_FILE_TREE::FindById(DWORD FileDataId) +{ + PCASC_FILE_NODE * RefElement; + PCASC_FILE_NODE pFileNode = NULL; + + if(FileDataId != CASC_INVALID_ID && FileDataIds.IsInitialized()) + { + // Insert the element to the array + RefElement = (PCASC_FILE_NODE *)FileDataIds.ItemAt(FileDataId); + if(RefElement != NULL) + { + pFileNode = RefElement[0]; + } + } + + return pFileNode; +} + +bool CASC_FILE_TREE::SetNodeFileName(PCASC_FILE_NODE pFileNode, const char * szFileName) +{ + ULONGLONG FileNameHash = 0; + PCASC_FILE_NODE pFolderNode = NULL; + const char * szNodeBegin = szFileName; + char szPathBuffer[MAX_PATH+1]; + size_t nFileNode = NodeTable.IndexOf(pFileNode); + size_t i; + DWORD Parent = 0; + + // Sanity checks + assert(szFileName != NULL && szFileName[0] != 0); + assert(pFileNode->pCKeyEntry != NULL); + + // Traverse the entire path. For each subfolder, we insert an appropriate fake entry + for(i = 0; szFileName[i] != 0; i++) + { + char chOneChar = szFileName[i]; + + // Is there a path separator? + // Note: Warcraft III paths may contain "mount points". + // Example: "frFR-War3Local.mpq:Maps/FrozenThrone/Campaign/NightElfX06Interlude.w3x:war3map.j" + if(chOneChar == '\\' || chOneChar == '/' || chOneChar == ':') + { + // Calculate hash of the file name up to the end of the node name + FileNameHash = CalcNormNameHash(szPathBuffer, i); + + // If the entry is not there yet, create new one + if((pFolderNode = Find(FileNameHash)) == NULL) + { + // Insert new entry to the tree + pFolderNode = InsertNew(); + if(pFolderNode == NULL) + return false; + + // Populate the file entry + pFolderNode->FileNameHash = FileNameHash; + pFolderNode->Parent = Parent; + pFolderNode->Flags |= (chOneChar == ':') ? CFN_FLAG_MOUNT_POINT : 0; + pFolderNode->Flags |= CFN_FLAG_FOLDER; + + // Set the node sub name to the node + SetNodePlainName(pFolderNode, szNodeBegin, szFileName + i); + + // Insert the entry to the name map + InsertToHashTable(pFolderNode); + } + + // Move the parent to the current node + Parent = (DWORD)NodeTable.IndexOf(pFolderNode); + + // Move the begin of the node after the separator + szNodeBegin = szFileName + i + 1; + } + + // Copy the next character, even if it was slash/backslash before + szPathBuffer[i] = AsciiToUpperTable_BkSlash[chOneChar]; + } + + // If anything left, this is gonna be our node name + if(szNodeBegin < szFileName + i) + { + // We need to reset the file node pointer, as the file node table might have changed + pFileNode = (PCASC_FILE_NODE)NodeTable.ItemAt(nFileNode); + + SetNodePlainName(pFileNode, szNodeBegin, szFileName + i); + pFileNode->Parent = Parent; + } + return true; +} + +size_t CASC_FILE_TREE::GetMaxFileIndex() +{ + if(FileDataIds.IsInitialized()) + { + return FileDataIds.ItemCount(); + } + else + { + return NodeTable.ItemCount(); + } +} + +size_t CASC_FILE_TREE::GetCount() +{ + return NodeTable.ItemCount(); +} + +size_t CASC_FILE_TREE::IndexOf(PCASC_FILE_NODE pFileNode) +{ + return NodeTable.IndexOf(pFileNode); +} + +void CASC_FILE_TREE::GetExtras(PCASC_FILE_NODE pFileNode, PDWORD PtrFileDataId, PDWORD PtrLocaleFlags, PDWORD PtrContentFlags) +{ + DWORD FileDataId = CASC_INVALID_ID; + DWORD LocaleFlags = CASC_INVALID_ID; + DWORD ContentFlags = CASC_INVALID_ID; + + // Retrieve the data ID, if supported + if(PtrFileDataId != NULL) + { + if(FileDataIdOffset != 0) + FileDataId = GET_NODE_INT32(pFileNode, FileDataIdOffset); + PtrFileDataId[0] = FileDataId; + } + + // Retrieve the locale ID, if supported + if(PtrLocaleFlags != NULL) + { + if(LocaleFlagsOffset != 0) + LocaleFlags = GET_NODE_INT32(pFileNode, LocaleFlagsOffset); + PtrLocaleFlags[0] = LocaleFlags; + } + + if(PtrContentFlags != NULL) + { + if(ContentFlagsOffset != 0) + ContentFlags = GET_NODE_INT32(pFileNode, ContentFlagsOffset); + PtrContentFlags[0] = ContentFlags; + } +} + +void CASC_FILE_TREE::SetExtras(PCASC_FILE_NODE pFileNode, DWORD FileDataId, DWORD LocaleFlags, DWORD ContentFlags) +{ + // Set the file data ID, if supported + if(FileDataIdOffset != 0) + { + SET_NODE_INT32(pFileNode, FileDataIdOffset, FileDataId); + } + + // Set the locale ID, if supported + if(LocaleFlagsOffset != 0) + { + SET_NODE_INT32(pFileNode, LocaleFlagsOffset, LocaleFlags); + } + + // Set the locale ID, if supported + if(ContentFlagsOffset != 0) + { + SET_NODE_INT32(pFileNode, ContentFlagsOffset, ContentFlags); + } +} diff --git a/dep/CascLib/src/common/FileTree.h b/dep/CascLib/src/common/FileTree.h new file mode 100644 index 00000000000..904da6c4c46 --- /dev/null +++ b/dep/CascLib/src/common/FileTree.h @@ -0,0 +1,116 @@ +/*****************************************************************************/ +/* FileTree.h Copyright (c) Ladislav Zezula 2018 */ +/*---------------------------------------------------------------------------*/ +/* Common implementation of a file tree object for various ROOt file formats */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* 29.05.18 1.00 Lad The first version of FileTree.h */ +/*****************************************************************************/ + +#ifndef __FILETREE_H__ +#define __FILETREE_H__ + +//----------------------------------------------------------------------------- +// Structures + +#define FTREE_FLAG_USE_DATA_ID 0x0001 // The FILE_NODE also contains file data ID +#define FTREE_FLAG_USE_LOCALE_FLAGS 0x0002 // The FILE_NODE also contains file locale flags +#define FTREE_FLAG_USE_CONTENT_FLAGS 0x0004 // The FILE_NODE also contains content flags + +#define CFN_FLAG_FOLDER 0x0001 // This item is a folder +#define CFN_FLAG_MOUNT_POINT 0x0002 // This item is a mount point. + +// Common structure for holding a single folder/file node +typedef struct _CASC_FILE_NODE +{ + ULONGLONG FileNameHash; // Jenkins hash of the normalized file name (uppercase, backslashes) + PCASC_CKEY_ENTRY pCKeyEntry; // Pointer to the CKey entry. + DWORD Parent; // The index of a parent directory. If CASC_INVALID_INDEX, then this is the root item + DWORD NameIndex; // Index of the node name. If CASC_INVALID_INDEX, then this node has no name + USHORT NameLength; // Length of the node name (without the zero terminator) + USHORT Flags; // See CFE_FLAG_XXX + + DWORD ExtraValues[4]; // FileDataId: Only if FTREE_FLAG_USE_DATA_ID specified at create + // LocaleFlags: Only if FTREE_FLAG_USE_LOCALE_FLAGS specified at create + // ContentFlags: Only if FTREE_FLAG_USE_CONTENT_FLAGS specified at create +} CASC_FILE_NODE, *PCASC_FILE_NODE; + +// Common structure for comparing a file node +typedef struct _CASC_COMPARE_CONTEXT +{ + ULONGLONG FileNameHash; + const char * szFileName; + void * pThis; +} CASC_COMPARE_CONTEXT, *PCASC_COMPARE_CONTEXT; + +// Main structure for the file tree +class CASC_FILE_TREE +{ + public: + + // Initializes/destroys the entire tree + int Create(DWORD Flags = 0); + void Free(); + + // Inserts a new node to the tree; either with name or nameless + PCASC_FILE_NODE InsertByName(PCASC_CKEY_ENTRY pCKeyEntry, const char * szFileName, DWORD FileDataId = CASC_INVALID_ID, DWORD LocaleFlags = CASC_INVALID_ID, DWORD ContentFlags = CASC_INVALID_ID); + PCASC_FILE_NODE InsertByHash(PCASC_CKEY_ENTRY pCKeyEntry, ULONGLONG FileNameHash, DWORD FileDataId, DWORD LocaleFlags = CASC_INVALID_ID, DWORD ContentFlags = CASC_INVALID_ID); + PCASC_FILE_NODE InsertById(PCASC_CKEY_ENTRY pCKeyEntry, DWORD FileDataId, DWORD LocaleFlags = CASC_INVALID_ID, DWORD ContentFlags = CASC_INVALID_ID); + + // Returns an item at the given index. The PathAt also builds the full path of the node + PCASC_FILE_NODE ItemAt(size_t nItemIndex); + PCASC_FILE_NODE PathAt(char * szBuffer, size_t cchBuffer, size_t nItemIndex); + size_t PathAt(char * szBuffer, size_t cchBuffer, PCASC_FILE_NODE pFileNode); + + // Finds a file using its full path, FileDataId or CKey/EKey + PCASC_FILE_NODE Find(const char * szFullPath, DWORD FileDataId, struct _CASC_FIND_DATA * pFindData); + PCASC_FILE_NODE Find(PCASC_CKEY_ENTRY pCKeyEntry); + PCASC_FILE_NODE Find(ULONGLONG FileNameHash); + PCASC_FILE_NODE FindById(DWORD FileDataId); + + // Assigns a file name to the node + bool SetNodeFileName(PCASC_FILE_NODE pFileNode, const char * szFileName); + + // Returns the number of items in the tree + size_t GetMaxFileIndex(); + size_t GetCount(); + + // Returns the index of an item in the tree + size_t IndexOf(PCASC_FILE_NODE pFileNode); + + // Retrieves the extra values from the node (if supported) + void GetExtras(PCASC_FILE_NODE pFileNode, PDWORD PtrFileDataId, PDWORD PtrLocaleFlags, PDWORD PtrContentFlags); + void SetExtras(PCASC_FILE_NODE pFileNode, DWORD FileDataId, DWORD LocaleFlags, DWORD ContentFlags); + + // Change the length of the key + bool SetKeyLength(DWORD KeyLength); + + // Retrieve the maximum FileDataId ever inserted + DWORD GetNextFileDataId(); + + protected: + + PCASC_FILE_NODE InsertNew(PCASC_CKEY_ENTRY pCKeyEntry); + PCASC_FILE_NODE InsertNew(); + bool InsertToHashTable(PCASC_FILE_NODE pFileNode); + bool InsertToIdTable(PCASC_FILE_NODE pFileNode); + + bool SetNodePlainName(PCASC_FILE_NODE pFileNode, const char * szPlainName, const char * szPlainNameEnd); + bool RebuildNameMaps(); + + CASC_ARRAY NodeTable; // Dynamic array that holds all CASC_FILE_NODEs + CASC_ARRAY NameTable; // Dynamic array that holds all node names + + CASC_ARRAY FileDataIds; // Dynamic array that maps FileDataId -> CASC_FILE_NODE + CASC_MAP NameMap; // Map of FileNameHash -> CASC_FILE_NODE + + size_t FileDataIdOffset; // If nonzero, this is the offset of the "FileDataId" field in the CASC_FILE_NODE + size_t LocaleFlagsOffset; // If nonzero, this is the offset of the "LocaleFlags" field in the CASC_FILE_NODE + size_t ContentFlagsOffset; // If nonzero, this is the offset of the "ContentFlags" field in the CASC_FILE_NODE + DWORD KeyLength; // Actual length of the key supported by the root handler +}; + +typedef CASC_FILE_TREE * PCASC_FILE_TREE; + +#endif // __FILETREE_H__ diff --git a/dep/CascLib/src/common/ListFile.cpp b/dep/CascLib/src/common/ListFile.cpp index dc9a88bf44c..351ea226188 100644 --- a/dep/CascLib/src/common/ListFile.cpp +++ b/dep/CascLib/src/common/ListFile.cpp @@ -15,11 +15,14 @@ //----------------------------------------------------------------------------- // Listfile cache structure +#define LISTFILE_FLAG_USES_FILEDATAID 0x0001 // A new CSV format, containing FileDataId; FullFileName + typedef struct _LISTFILE_CACHE { - char * pBegin; // The begin of the listfile cache - char * pPos; // Current position in the cache - char * pEnd; // The last character in the file cache + char * pBegin; // The begin of the listfile cache + char * pPos; // Current position in the cache + char * pEnd; // The last character in the file cache + DWORD Flags; // Followed by the cache (variable length) @@ -28,7 +31,7 @@ typedef struct _LISTFILE_CACHE //----------------------------------------------------------------------------- // Creating the listfile cache for the given amount of data -static PLISTFILE_CACHE CreateListFileCache(DWORD dwFileSize) +static PLISTFILE_CACHE ListFile_CreateCache(DWORD dwFileSize) { PLISTFILE_CACHE pCache; @@ -40,12 +43,81 @@ static PLISTFILE_CACHE CreateListFileCache(DWORD dwFileSize) pCache->pBegin = pCache->pPos = (char *)(pCache + 1); pCache->pEnd = pCache->pBegin + dwFileSize; + pCache->Flags = 0; } // Return the cache return pCache; } +static char * ListFile_SkipSpaces(PLISTFILE_CACHE pCache) +{ + // Skip newlines, spaces, tabs and another non-printable stuff + while(pCache->pPos < pCache->pEnd && pCache->pPos[0] <= 0x20) + pCache->pPos++; + + // Remember the begin of the line + return pCache->pPos; +} + +static void ListFile_CheckFormat(PLISTFILE_CACHE pCache) +{ + // Only if the listfile is greatger than 2 MB + if((pCache->pEnd - pCache->pBegin) > 0x100000) + { + char * szPtr = pCache->pBegin; + size_t nDigitCount = 0; + + // Calculate the amount of digits + while(nDigitCount <= 20 && '0' <= szPtr[nDigitCount] && szPtr[nDigitCount] <= '9') + nDigitCount++; + + // There must be a semicolon after + if(nDigitCount <= 10 && szPtr[nDigitCount] == ';') + { + pCache->Flags |= LISTFILE_FLAG_USES_FILEDATAID; + } + } +} + +static int ListFile_GetFileDataId(PLISTFILE_CACHE pCache, PDWORD PtrFileDataId) +{ + char * szLineBegin = ListFile_SkipSpaces(pCache); + char * szLineEnd; + DWORD dwNewInt32 = 0; + DWORD dwInt32 = 0; + + // Set the limit for loading the number + szLineEnd = CASCLIB_MIN((szLineBegin + 20), pCache->pEnd); + + // Extract decimal digits from the string + while(szLineBegin < szLineEnd && '0' <= szLineBegin[0] && szLineBegin[0] <= '9') + { + // Check integer overflow + dwNewInt32 = (dwInt32 * 10) + (szLineBegin[0] - '0'); + if(dwNewInt32 < dwInt32) + return ERROR_BAD_FORMAT; + + dwInt32 = dwNewInt32; + szLineBegin++; + } + + // There must still be some space + if(szLineBegin < szLineEnd) + { + // There must be a semicolon after the decimal integer + // The decimal integer must be smaller than 10 MB (files) + if(szLineBegin[0] != ';' || dwInt32 >= 0xA00000) + return ERROR_BAD_FORMAT; + + pCache->pPos = szLineBegin + 1; + PtrFileDataId[0] = dwInt32; + return ERROR_SUCCESS; + } + + return ERROR_NO_MORE_FILES; +} + //----------------------------------------------------------------------------- // Functions for parsing an external listfile @@ -65,13 +137,16 @@ void * ListFile_OpenExternal(const TCHAR * szListFile) { // Create the in-memory cache for the entire listfile // The listfile does not have any data loaded yet - pCache = CreateListFileCache((DWORD)FileSize); + pCache = ListFile_CreateCache((DWORD)FileSize); if(pCache != NULL) { - if(!FileStream_Read(pStream, NULL, pCache->pBegin, (DWORD)FileSize)) + if(FileStream_Read(pStream, NULL, pCache->pBegin, (DWORD)FileSize)) { - ListFile_Free(pCache); - pCache = NULL; + ListFile_CheckFormat(pCache); + } + else + { + CASC_FREE(pCache); } } } @@ -89,7 +164,7 @@ void * ListFile_FromBuffer(LPBYTE pbBuffer, DWORD cbBuffer) // Create the in-memory cache for the entire listfile // The listfile does not have any data loaded yet - pCache = CreateListFileCache(cbBuffer); + pCache = ListFile_CreateCache(cbBuffer); if(pCache != NULL) memcpy(pCache->pBegin, pbBuffer, cbBuffer); @@ -105,7 +180,7 @@ bool ListFile_VerifyMD5(void * pvListFile, LPBYTE pbHashMD5) assert(pCache->pPos == pCache->pBegin); // Verify the MD5 hash for the entire block - return VerifyDataBlockHash(pCache->pBegin, (DWORD)(pCache->pEnd - pCache->pBegin), pbHashMD5); + return CascVerifyDataBlockHash(pCache->pBegin, (DWORD)(pCache->pEnd - pCache->pBegin), pbHashMD5); } size_t ListFile_GetNextLine(void * pvListFile, const char ** pszLineBegin, const char ** pszLineEnd) @@ -116,17 +191,15 @@ size_t ListFile_GetNextLine(void * pvListFile, const char ** pszLineBegin, const char * szLineEnd; // Skip newlines, spaces, tabs and another non-printable stuff - while(pCache->pPos < pCache->pEnd && pCache->pPos[0] <= 0x20) - pCache->pPos++; - // Remember the begin of the line - szLineBegin = pCache->pPos; + szLineBegin = ListFile_SkipSpaces(pCache); // Copy the remaining characters while(pCache->pPos < pCache->pEnd) { // If we have found a newline, stop loading - if(pCache->pPos[0] == 0x0D || pCache->pPos[0] == 0x0A) + // Note: the 0x85 char came from Overwatch build 24919 + if(pCache->pPos[0] == 0x0A || pCache->pPos[0] == 0x0D || pCache->pPos[0] == 0x85) break; // Blizzard listfiles can also contain information about patch: @@ -169,14 +242,28 @@ size_t ListFile_GetNextLine(void * pvListFile, char * szBuffer, size_t nMaxChars return nLength; } -size_t ListFile_GetNext(void * pvListFile, const char * szMask, char * szBuffer, size_t nMaxChars) +size_t ListFile_GetNext(void * pvListFile, char * szBuffer, size_t nMaxChars, PDWORD PtrFileDataId) { + PLISTFILE_CACHE pCache = (PLISTFILE_CACHE)pvListFile; size_t nLength = 0; int nError = ERROR_SUCCESS; // Check for parameters for(;;) { + DWORD FileDataId = CASC_INVALID_ID; + + // If this is a CSV-format listfile, we need to extract the FileDataId + // Lines that contain bogus data, invalid numbers or too big values will be skipped + if(pCache->Flags & LISTFILE_FLAG_USES_FILEDATAID) + { + nError = ListFile_GetFileDataId(pCache, &FileDataId); + if(nError == ERROR_NO_MORE_FILES) + break; + if(nError != ERROR_SUCCESS || FileDataId == CASC_INVALID_ID) + continue; + } + // Read the (next) line nLength = ListFile_GetNextLine(pvListFile, szBuffer, nMaxChars); if(nLength == 0) @@ -185,12 +272,9 @@ size_t ListFile_GetNext(void * pvListFile, const char * szMask, char * szBuffer, break; } - // If some mask entered, check it - if(CheckWildCard(szBuffer, szMask)) - { - nError = ERROR_SUCCESS; - break; - } + // Give the file data id and return true + PtrFileDataId[0] = FileDataId; + return nLength; } if(nError != ERROR_SUCCESS) @@ -198,163 +282,22 @@ size_t ListFile_GetNext(void * pvListFile, const char * szMask, char * szBuffer, return nLength; } -void ListFile_Free(void * pvListFile) +LPBYTE ListFile_GetData(void * pvListFile, PDWORD PtrDataSize) { - if(pvListFile != NULL) - { - CASC_FREE(pvListFile); - } -} - -//----------------------------------------------------------------------------- -// Functions for creating a listfile map - -#define LISTMAP_INITIAL 0x100000 - -static PLISTFILE_MAP ListMap_Create() -{ - PLISTFILE_MAP pListMap; - size_t cbToAllocate; - - // Create buffer for the listfile - // Note that because the listfile is quite big and CASC_REALLOC - // is a costly operation, we want to have as few reallocs as possible. - cbToAllocate = sizeof(LISTFILE_MAP) + LISTMAP_INITIAL; - pListMap = (PLISTFILE_MAP)CASC_ALLOC(BYTE, cbToAllocate); - if(pListMap != NULL) - { - // Fill the listfile buffer - memset(pListMap, 0, sizeof(LISTFILE_MAP)); - pListMap->cbBufferMax = LISTMAP_INITIAL; - } - - return pListMap; -} - -static PLISTFILE_MAP ListMap_InsertName(PLISTFILE_MAP pListMap, const char * szFileName, size_t nLength) -{ - PLISTFILE_ENTRY pListEntry; - size_t cbToAllocate; - size_t cbEntrySize; - - // Make sure there is enough space in the list map - cbEntrySize = sizeof(LISTFILE_ENTRY) + nLength; - cbEntrySize = ALIGN_TO_SIZE(cbEntrySize, 8); - if((pListMap->cbBuffer + cbEntrySize) > pListMap->cbBufferMax) - { - cbToAllocate = sizeof(LISTFILE_MAP) + (pListMap->cbBufferMax * 3) / 2; - pListMap = (PLISTFILE_MAP)CASC_REALLOC(BYTE, pListMap, cbToAllocate); - if(pListMap == NULL) - return NULL; - - pListMap->cbBufferMax = (pListMap->cbBufferMax * 3) / 2; - } - - // Get the pointer to the first entry - pListEntry = (PLISTFILE_ENTRY)((LPBYTE)(pListMap + 1) + pListMap->cbBuffer); - pListEntry->FileNameHash = CalcFileNameHash(szFileName); - pListEntry->cbEntrySize = (DWORD)cbEntrySize; - - // Copy the file name to the entry - memcpy(pListEntry->szFileName, szFileName, nLength); - pListEntry->szFileName[nLength] = 0; - - // Move the next entry - pListMap->cbBuffer += cbEntrySize; - pListMap->nEntries++; - return pListMap; -} - -static PLISTFILE_MAP ListMap_Finish(PLISTFILE_MAP pListMap) -{ - PLISTFILE_ENTRY pListEntry; - PCASC_MAP pMap; - LPBYTE pbEntry; - - // Sanity check - assert(pListMap->pNameMap == NULL); - - // Create the map - pListMap->pNameMap = pMap = Map_Create((DWORD)pListMap->nEntries, sizeof(ULONGLONG), 0); - if(pListMap->pNameMap == NULL) - { - ListFile_FreeMap(pListMap); - return NULL; - } - - // Fill the map - pbEntry = (LPBYTE)(pListMap + 1); - for(size_t i = 0; i < pListMap->nEntries; i++) - { - // Get the listfile entry - pListEntry = (PLISTFILE_ENTRY)pbEntry; - pbEntry += pListEntry->cbEntrySize; - - // Insert the entry to the map - Map_InsertObject(pMap, pListEntry, &pListEntry->FileNameHash); - } - - return pListMap; -} - -PLISTFILE_MAP ListFile_CreateMap(const TCHAR * szListFile) -{ - PLISTFILE_MAP pListMap = NULL; - void * pvListFile; - char szFileName[MAX_PATH+1]; - size_t nLength; + PLISTFILE_CACHE pCache = (PLISTFILE_CACHE)pvListFile; + LPBYTE pbData = NULL; + DWORD cbData = 0; - // Only if the listfile name has been given - if(szListFile != NULL) + // Get data from the list file cache + if (pvListFile != NULL) { - // Create map for the listfile - pListMap = ListMap_Create(); - if(pListMap != NULL) - { - // Open the external listfile - pvListFile = ListFile_OpenExternal(szListFile); - if(pvListFile != NULL) - { - // Go through the entire listfile and insert each name to the map - while((nLength = ListFile_GetNext(pvListFile, "*", szFileName, MAX_PATH)) != 0) - { - // Insert the file name to the map - pListMap = ListMap_InsertName(pListMap, szFileName, nLength); - if(pListMap == NULL) - break; - } - - if(pListMap == NULL) - { - // Finish the listfile map - pListMap = ListMap_Finish(pListMap); - } - - // Free the listfile - ListFile_Free(pvListFile); - } - } + pbData = (LPBYTE)pCache->pBegin; + cbData = (DWORD)(pCache->pEnd - pCache->pBegin); } - // Return the created map - return pListMap; -} - -const char * ListFile_FindName(PLISTFILE_MAP pListMap, ULONGLONG FileNameHash) -{ - PLISTFILE_ENTRY pListEntry = NULL; - - if(pListMap != NULL) - pListEntry = (PLISTFILE_ENTRY)Map_FindObject(pListMap->pNameMap, &FileNameHash, NULL); - return (pListEntry != NULL) ? pListEntry->szFileName : ""; + // Give the data to the caller + if (PtrDataSize != NULL) + PtrDataSize[0] = cbData; + return pbData; } -void ListFile_FreeMap(PLISTFILE_MAP pListMap) -{ - if(pListMap != NULL) - { - if(pListMap->pNameMap != NULL) - Map_Free(pListMap->pNameMap); - CASC_FREE(pListMap); - } -} diff --git a/dep/CascLib/src/common/ListFile.h b/dep/CascLib/src/common/ListFile.h index 84bec3ed751..1333b52c2b5 100644 --- a/dep/CascLib/src/common/ListFile.h +++ b/dep/CascLib/src/common/ListFile.h @@ -12,28 +12,6 @@ #define __LISTFILE_H__ //----------------------------------------------------------------------------- -// Structures - -typedef struct _LISTFILE_ENTRY -{ - ULONGLONG FileNameHash; // Hash of the file name - DWORD cbEntrySize; // Length of this entry, in bytes - char szFileName[1]; // File name, aligned to 8-byte boundary - -} LISTFILE_ENTRY, *PLISTFILE_ENTRY; - -typedef struct _LISTFILE_MAP -{ - PCASC_MAP pNameMap; // Map of hash-to-name - size_t cbBufferMax; // Total size of the buffer, in bytes - size_t cbBuffer; // Current size of the buffer, in bytes - size_t nEntries; // Number of entries - - // First LISTFILE_ENTRY starts here - -} LISTFILE_MAP, *PLISTFILE_MAP; - -//----------------------------------------------------------------------------- // Functions for parsing an external listfile void * ListFile_OpenExternal(const TCHAR * szListFile); @@ -41,14 +19,7 @@ void * ListFile_FromBuffer(LPBYTE pbBuffer, DWORD cbBuffer); bool ListFile_VerifyMD5(void * pvListFile, LPBYTE pbHashMD5); size_t ListFile_GetNextLine(void * pvListFile, const char ** pszLineBegin, const char ** pszLineEnd); size_t ListFile_GetNextLine(void * pvListFile, char * szBuffer, size_t nMaxChars); -size_t ListFile_GetNext(void * pvListFile, const char * szMask, char * szBuffer, size_t nMaxChars); -void ListFile_Free(void * pvListFile); - -//----------------------------------------------------------------------------- -// Functions for creating a listfile map - -PLISTFILE_MAP ListFile_CreateMap(const TCHAR * szListFile); -const char * ListFile_FindName(PLISTFILE_MAP pListMap, ULONGLONG FileNameHash); -void ListFile_FreeMap(PLISTFILE_MAP pListMap); +size_t ListFile_GetNext(void * pvListFile, char * szBuffer, size_t nMaxChars, PDWORD PtrFileDataId); +LPBYTE ListFile_GetData(void * pvListFile, PDWORD PtrDataSize); #endif // __LISTFILE_H__ diff --git a/dep/CascLib/src/common/Map.cpp b/dep/CascLib/src/common/Map.cpp deleted file mode 100644 index aa1e2a2ada1..00000000000 --- a/dep/CascLib/src/common/Map.cpp +++ /dev/null @@ -1,287 +0,0 @@ -/*****************************************************************************/ -/* Map.cpp Copyright (c) Ladislav Zezula 2014 */ -/*---------------------------------------------------------------------------*/ -/* Implementation of map for CascLib */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* 10.06.14 1.00 Lad The first version of Map.cpp */ -/*****************************************************************************/ - -#define __CASCLIB_SELF__ -#include "../CascLib.h" -#include "../CascCommon.h" - -//----------------------------------------------------------------------------- -// Local functions - -// Returns the extension, right after "." -static const char * String_GetExtension(const char * szString) -{ - const char * szExtension = strrchr(szString, '.'); - return (szExtension != NULL) ? szExtension + 1 : NULL; -} - -static DWORD CalcHashIndex_Key(PCASC_MAP pMap, void * pvKey) -{ - LPBYTE pbKey = (LPBYTE)pvKey; - DWORD dwHash = 0x7EEE7EEE; - - // Construct the hash from the first 8 digits - dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[0]; - dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[1]; - dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[2]; - dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[3]; - dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[4]; - dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[5]; - dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[6]; - dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[7]; - - // Return the hash limited by the table size - return (dwHash % pMap->TableSize); -} - -static DWORD CalcHashIndex_String(PCASC_MAP pMap, const char * szString, const char * szStringEnd) -{ - LPBYTE pbKeyEnd = (LPBYTE)szStringEnd; - LPBYTE pbKey = (LPBYTE)szString; - DWORD dwHash = 0x7EEE7EEE; - - // Hash the string itself - while(pbKey < pbKeyEnd) - { - dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ AsciiToUpperTable_BkSlash[pbKey[0]]; - pbKey++; - } - - // Return the hash limited by the table size - return (dwHash % pMap->TableSize); -} - -static bool CompareObject_Key(PCASC_MAP pMap, void * pvObject, void * pvKey) -{ - LPBYTE pbObjectKey = (LPBYTE)pvObject + pMap->KeyOffset; - - return (memcmp(pbObjectKey, pvKey, pMap->KeyLength) == 0); -} - -static bool CompareObject_String( - PCASC_MAP pMap, - const char * szExistingString, - const char * szString, - const char * szStringEnd) -{ - // Keep compiler happy - CASCLIB_UNUSED(pMap); - - // Compare the whole part, case insensitive - while(szString < szStringEnd) - { - if(AsciiToUpperTable_BkSlash[*szExistingString] != AsciiToUpperTable_BkSlash[*szString]) - return false; - - szExistingString++; - szString++; - } - - return true; -} - -//----------------------------------------------------------------------------- -// Public functions - -PCASC_MAP Map_Create(DWORD dwMaxItems, DWORD dwKeyLength, DWORD dwKeyOffset) -{ - PCASC_MAP pMap; - size_t cbToAllocate; - size_t dwTableSize; - - // Calculate the size of the table - dwTableSize = (dwMaxItems * 3 / 2) | 0x01; - - // Allocate new map for the objects - cbToAllocate = sizeof(CASC_MAP) + (dwTableSize * sizeof(void *)); - pMap = (PCASC_MAP)CASC_ALLOC(LPBYTE, cbToAllocate); - if(pMap != NULL) - { - memset(pMap, 0, cbToAllocate); - pMap->KeyLength = dwKeyLength; - pMap->TableSize = dwTableSize; - pMap->KeyOffset = dwKeyOffset; - } - - // Return the allocated map - return pMap; -} - -size_t Map_EnumObjects(PCASC_MAP pMap, void **ppvArray) -{ - size_t nIndex = 0; - - // Verify pointer to the map - if(pMap != NULL && ppvArray != NULL) - { - // Enumerate all items in main table - for(size_t i = 0; i < pMap->TableSize; i++) - { - // Is that cell valid? - if(pMap->HashTable[i] != NULL) - { - ppvArray[nIndex++] = pMap->HashTable[i]; - } - } - - return pMap->ItemCount; - } - - return 0; -} - -void * Map_FindObject(PCASC_MAP pMap, void * pvKey, PDWORD PtrIndex) -{ - void * pvObject; - DWORD dwHashIndex; - - // Verify pointer to the map - if(pMap != NULL) - { - // Construct the main index - dwHashIndex = CalcHashIndex_Key(pMap, pvKey); - while(pMap->HashTable[dwHashIndex] != NULL) - { - // Get the pointer at that position - pvObject = pMap->HashTable[dwHashIndex]; - - // Compare the hash - if(CompareObject_Key(pMap, pvObject, pvKey)) - { - if(PtrIndex != NULL) - PtrIndex[0] = dwHashIndex; - return pvObject; - } - - // Move to the next entry - dwHashIndex = (dwHashIndex + 1) % pMap->TableSize; - } - } - - // Not found, sorry - return NULL; -} - -bool Map_InsertObject(PCASC_MAP pMap, void * pvNewObject, void * pvKey) -{ - void * pvExistingObject; - DWORD dwHashIndex; - - // Verify pointer to the map - if(pMap != NULL) - { - // Limit check - if((pMap->ItemCount + 1) >= pMap->TableSize) - return false; - - // Construct the hash index - dwHashIndex = CalcHashIndex_Key(pMap, pvKey); - while(pMap->HashTable[dwHashIndex] != NULL) - { - // Get the pointer at that position - pvExistingObject = pMap->HashTable[dwHashIndex]; - - // Check if hash being inserted conflicts with an existing hash - if(CompareObject_Key(pMap, pvExistingObject, pvKey)) - return false; - - // Move to the next entry - dwHashIndex = (dwHashIndex + 1) % pMap->TableSize; - } - - // Insert at that position - pMap->HashTable[dwHashIndex] = pvNewObject; - pMap->ItemCount++; - return true; - } - - // Failed - return false; -} - -bool Map_InsertString(PCASC_MAP pMap, const char * szString, bool bCutExtension) -{ - const char * szExistingString; - const char * szStringEnd = NULL; - DWORD dwHashIndex; - - // Verify pointer to the map - if(pMap != NULL) - { - // Limit check - if((pMap->ItemCount + 1) >= pMap->TableSize) - return false; - - // Retrieve the length of the string without extension - if(bCutExtension) - szStringEnd = String_GetExtension(szString); - if(szStringEnd == NULL) - szStringEnd = szString + strlen(szString); - - // Construct the hash index - dwHashIndex = CalcHashIndex_String(pMap, szString, szStringEnd); - while(pMap->HashTable[dwHashIndex] != NULL) - { - // Get the pointer at that position - szExistingString = (const char *)pMap->HashTable[dwHashIndex]; - - // Check if hash being inserted conflicts with an existing hash - if(CompareObject_String(pMap, szExistingString, szString, szStringEnd)) - return false; - - // Move to the next entry - dwHashIndex = (dwHashIndex + 1) % pMap->TableSize; - } - - // Insert at that position - pMap->HashTable[dwHashIndex] = (void *)szString; - pMap->ItemCount++; - return true; - } - - // Failed - return false; -} - -const char * Map_FindString(PCASC_MAP pMap, const char * szString, const char * szStringEnd) -{ - const char * szExistingString; - DWORD dwHashIndex; - - // Verify pointer to the map - if(pMap != NULL) - { - // Construct the main index - dwHashIndex = CalcHashIndex_String(pMap, szString, szStringEnd); - while(pMap->HashTable[dwHashIndex] != NULL) - { - // Get the pointer at that position - szExistingString = (const char *)pMap->HashTable[dwHashIndex]; - - // Compare the hash - if(CompareObject_String(pMap, szExistingString, szString, szStringEnd)) - return szExistingString; - - // Move to the next entry - dwHashIndex = (dwHashIndex + 1) % pMap->TableSize; - } - } - - // Not found, sorry - return NULL; -} - -void Map_Free(PCASC_MAP pMap) -{ - if(pMap != NULL) - { - CASC_FREE(pMap); - } -} diff --git a/dep/CascLib/src/common/Map.h b/dep/CascLib/src/common/Map.h index 2b590578105..13799eb0528 100644 --- a/dep/CascLib/src/common/Map.h +++ b/dep/CascLib/src/common/Map.h @@ -8,35 +8,355 @@ /* 10.06.14 1.00 Lad The first version of Map.h */ /*****************************************************************************/ -#ifndef __HASHTOPTR_H__ -#define __HASHTOPTR_H__ +#ifndef __CASC_MAP_H__ +#define __CASC_MAP_H__ //----------------------------------------------------------------------------- // Structures -#define KEY_LENGTH_STRING 0xFFFFFFFF // Pass this to Map_Create as dwKeyLength when you want map of string->object +#define MIN_HASH_TABLE_SIZE 0x00000100 // The smallest size of the hash table. +#define MAX_HASH_TABLE_SIZE 0x00800000 // The largest size of the hash table. Should be enough for any game. -typedef struct _CASC_MAP +typedef int (*PFNCOMPAREFUNC)(const void * pvObjectKey, const void * pvKey, size_t nKeyLength); +typedef DWORD (*PFNHASHFUNC)(void * pvKey, size_t nKeyLength); + +typedef enum _KEY_TYPE +{ + KeyIsHash, // Use when the key is already a hash. If specified, the map will not hash the key + KeyIsArbitrary, // The key is an arbitrary array of bytes. The map will hash it to get a map index + KeyIsString // Use when the key is a string +} KEY_TYPE, *PKEY_TYPE; + +#define KEY_LENGTH_STRING 0xFFFFFFFF + +//----------------------------------------------------------------------------- +// Hashing functions + +// Used when the key is a hash already - no need to hash it again +inline DWORD CalcHashValue_Hash(void * pvKey, size_t /* nKeyLength */) +{ + // Get the hash directly as value + return ConvertBytesToInteger_4((LPBYTE)pvKey); +} + +// Calculates hash value from a key +inline DWORD CalcHashValue_Key(void * pvKey, size_t nKeyLength) { - size_t TableSize; - size_t ItemCount; // Number of items in the map - size_t KeyOffset; // How far is the hash from the begin of the structure (in bytes) - size_t KeyLength; // Length of the hash key - void * HashTable[1]; // Pointer table + LPBYTE pbKey = (LPBYTE)pvKey; + DWORD dwHash = 0x7EEE7EEE; -} CASC_MAP, *PCASC_MAP; + // Construct the hash from the key + for(DWORD i = 0; i < nKeyLength; i++) + dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ pbKey[i]; -typedef bool (*MAP_COMPARE)(PCASC_MAP pMap, void * pvObject, void * pvKey); + // Return the hash limited by the table size + return dwHash; +} + +// Calculates hash value from a string +inline DWORD CalcHashValue_String(const char * szString, const char * szStringEnd) +{ + LPBYTE pbKeyEnd = (LPBYTE)szStringEnd; + LPBYTE pbKey = (LPBYTE)szString; + DWORD dwHash = 0x7EEE7EEE; + + // Hash the string itself + while(pbKey < pbKeyEnd) + { + dwHash = (dwHash >> 24) ^ (dwHash << 5) ^ dwHash ^ AsciiToUpperTable_BkSlash[pbKey[0]]; + pbKey++; + } + + // Return the hash limited by the table size + return dwHash; +} //----------------------------------------------------------------------------- -// Functions +// Map implementation + +class CASC_MAP +{ + public: + + CASC_MAP() + { + PfnCalcHashValue = NULL; + m_HashTable = NULL; + m_HashTableSize = 0; + m_ItemCount = 0; + m_KeyOffset = 0; + m_KeyLength = 0; + m_bKeyIsHash = false; + } + + ~CASC_MAP() + { + Free(); + } + + int Create(size_t MaxItems, size_t KeyLength, size_t KeyOffset, KEY_TYPE KeyType = KeyIsHash) + { + // Set the class variables + m_KeyLength = CASCLIB_MAX(KeyLength, 8); + m_KeyOffset = KeyOffset; + m_ItemCount = 0; + + // Setup the hashing function + switch(KeyType) + { + case KeyIsHash: + PfnCalcHashValue = CalcHashValue_Hash; + break; + + case KeyIsArbitrary: + PfnCalcHashValue = CalcHashValue_Key; + break; + + case KeyIsString: + PfnCalcHashValue = NULL; + break; + + default: + assert(false); + return ERROR_NOT_SUPPORTED; + } + + // Calculate the hash table size. Take 133% of the item count and round it up to the next power of two + // This will make the hash table indexes somewhat more resilient against count changes and will make + // e.g. file order in the file tree more stable. + m_HashTableSize = GetNearestPowerOfTwo(MaxItems * 4 / 3); + if(m_HashTableSize == 0) + return ERROR_NOT_ENOUGH_MEMORY; + + // Allocate new map for the objects + m_HashTable = (void **)CASC_ALLOC(void *, m_HashTableSize); + if(m_HashTable == NULL) + return ERROR_NOT_ENOUGH_MEMORY; + + // Initialize the map object + memset(m_HashTable, 0, m_HashTableSize * sizeof(void *)); + return ERROR_SUCCESS; + } + + void * FindObject(void * pvKey, PDWORD PtrIndex = NULL) + { + void * pvObject; + DWORD dwHashIndex; + + // Verify pointer to the map + if(m_HashTable != NULL) + { + // Construct the hash index + dwHashIndex = HashToIndex(PfnCalcHashValue(pvKey, m_KeyLength)); + + // Search the hash table + while((pvObject = m_HashTable[dwHashIndex]) != NULL) + { + // Compare the hash + if(CompareObject_Key(pvObject, pvKey)) + { + if(PtrIndex != NULL) + PtrIndex[0] = dwHashIndex; + return pvObject; + } + + // Move to the next entry + dwHashIndex = HashToIndex(dwHashIndex + 1); + } + } + + // Not found, sorry + return NULL; + } + + bool InsertObject(void * pvNewObject, void * pvKey) + { + void * pvExistingObject; + DWORD dwHashIndex; + + // Verify pointer to the map + if(m_HashTable != NULL) + { + // Limit check + if((m_ItemCount + 1) >= m_HashTableSize) + return false; + + // Construct the hash index + dwHashIndex = HashToIndex(PfnCalcHashValue(pvKey, m_KeyLength)); + + // Search the hash table + while((pvExistingObject = m_HashTable[dwHashIndex]) != NULL) + { + // Check if hash being inserted conflicts with an existing hash + if(CompareObject_Key(pvExistingObject, pvKey)) + return false; + + // Move to the next entry + dwHashIndex = HashToIndex(dwHashIndex + 1); + } + + // Insert at that position + m_HashTable[dwHashIndex] = pvNewObject; + m_ItemCount++; + return true; + } + + // Failed + return false; + } + + const char * FindString(const char * szString, const char * szStringEnd) + { + const char * szExistingString; + DWORD dwHashIndex; + + // Verify pointer to the map + if(m_HashTable != NULL) + { + // Construct the main index + dwHashIndex = HashToIndex(CalcHashValue_String(szString, szStringEnd)); + + // Search the hash table + while((szExistingString = (const char *)m_HashTable[dwHashIndex]) != NULL) + { + // Compare the hash + if(CompareObject_String(szExistingString, szString, szStringEnd)) + return szExistingString; + + // Move to the next entry + dwHashIndex = HashToIndex(dwHashIndex + 1); + } + } + + // Not found, sorry + return NULL; + } + + bool InsertString(const char * szString, bool bCutExtension) + { + const char * szExistingString; + const char * szStringEnd = NULL; + DWORD dwHashIndex; + + // Verify pointer to the map + if(m_HashTable != NULL) + { + // Limit check + if((m_ItemCount + 1) >= m_HashTableSize) + return false; + + // Retrieve the length of the string without extension + if(bCutExtension) + szStringEnd = GetFileExtension(szString); + else + szStringEnd = szString + strlen(szString); + + // Construct the hash index + dwHashIndex = HashToIndex(CalcHashValue_String(szString, szStringEnd)); + + // Search the hash table + while((szExistingString = (const char *)m_HashTable[dwHashIndex]) != NULL) + { + // Check if hash being inserted conflicts with an existing hash + if(CompareObject_String(szExistingString, szString, szStringEnd)) + return false; + + // Move to the next entry + dwHashIndex = HashToIndex(dwHashIndex + 1); + } + + // Insert at that position + m_HashTable[dwHashIndex] = (void *)szString; + m_ItemCount++; + return true; + } + + // Failed + return false; + } + + void * ItemAt(size_t nIndex) + { + assert(nIndex < m_HashTableSize); + return m_HashTable[nIndex]; + } + + size_t HashTableSize() + { + return m_HashTableSize; + } + + size_t ItemCount() + { + return m_ItemCount; + } + + bool IsInitialized() + { + return (m_HashTable && m_HashTableSize); + } + + void Free() + { + PfnCalcHashValue = NULL; + CASC_FREE(m_HashTable); + m_HashTableSize = 0; + } + + protected: + + DWORD HashToIndex(DWORD HashValue) + { + return HashValue & (m_HashTableSize - 1); + } + + bool CompareObject_Key(void * pvObject, void * pvKey) + { + LPBYTE pbObjectKey = (LPBYTE)pvObject + m_KeyOffset; + return (memcmp(pbObjectKey, pvKey, m_KeyLength) == 0); + } + + bool CompareObject_String(const char * szExistingString, const char * szString, const char * szStringEnd) + { + // Compare the whole part, case insensitive + while(szString < szStringEnd) + { + if(AsciiToUpperTable_BkSlash[*szExistingString] != AsciiToUpperTable_BkSlash[*szString]) + return false; + + szExistingString++; + szString++; + } + + return true; + } + + size_t GetNearestPowerOfTwo(size_t MaxItems) + { + size_t PowerOfTwo; + + // Round the hash table size up to the nearest power of two + for(PowerOfTwo = MIN_HASH_TABLE_SIZE; PowerOfTwo < MAX_HASH_TABLE_SIZE; PowerOfTwo <<= 1) + { + if(PowerOfTwo > MaxItems) + { + return PowerOfTwo; + } + } + + // If the hash table is too big, we cannot create the map + assert(false); + return 0; + } -PCASC_MAP Map_Create(DWORD dwMaxItems, DWORD dwKeyLength, DWORD dwKeyOffset); -size_t Map_EnumObjects(PCASC_MAP pMap, void **ppvArray); -void * Map_FindObject(PCASC_MAP pMap, void * pvKey, PDWORD PtrIndex); -bool Map_InsertObject(PCASC_MAP pMap, void * pvNewObject, void * pvKey); -const char * Map_FindString(PCASC_MAP pMap, const char * szString, const char * szStringEnd); -bool Map_InsertString(PCASC_MAP pMap, const char * szString, bool bCutExtension); -void Map_Free(PCASC_MAP pMap); + PFNHASHFUNC PfnCalcHashValue; + void ** m_HashTable; // Hash table + size_t m_HashTableSize; // Size of the hash table, in entries. Always a power of two. + size_t m_ItemCount; // Number of objects in the map + size_t m_KeyOffset; // How far is the hash from the begin of the objects (in bytes) + size_t m_KeyLength; // Length of the hash key, in bytes + bool m_bKeyIsHash; // If set, then it means that the key is a hash of some sort. + // Will improve performance, as we will not hash a hash :-) +}; -#endif // __HASHTOPTR_H__ +#endif // __CASC_MAP_H__ diff --git a/dep/CascLib/src/common/RootHandler.cpp b/dep/CascLib/src/common/RootHandler.cpp index 4baeb41a421..89b693f55cb 100644 --- a/dep/CascLib/src/common/RootHandler.cpp +++ b/dep/CascLib/src/common/RootHandler.cpp @@ -13,76 +13,105 @@ #include "../CascCommon.h" //----------------------------------------------------------------------------- -// Common support +// Constructor and destructor - TFileTreeRoot -int RootHandler_Insert(TRootHandler * pRootHandler, const char * szFileName, LPBYTE pbEncodingKey) +TFileTreeRoot::TFileTreeRoot(DWORD FileTreeFlags) : TRootHandler() { - if(pRootHandler == NULL || pRootHandler->Insert == NULL || pbEncodingKey == NULL) - return ERROR_NOT_SUPPORTED; + // Initialize the file tree + FileTree.Create(FileTreeFlags); +} - return pRootHandler->Insert(pRootHandler, szFileName, pbEncodingKey); +TFileTreeRoot::~TFileTreeRoot() +{ + // Free the file tree + FileTree.Free(); + dwFeatures = 0; } -LPBYTE RootHandler_Search(TRootHandler * pRootHandler, struct _TCascSearch * pSearch, PDWORD PtrFileSize, PDWORD PtrLocaleFlags, PDWORD PtrFileDataId) +//----------------------------------------------------------------------------- +// Virtual functions - TFileTreeRoot + +int TFileTreeRoot::Insert( + const char * szFileName, + PCASC_CKEY_ENTRY pCKeyEntry) { - // Check if the root structure is valid at all - if(pRootHandler == NULL) - return NULL; + PCASC_FILE_NODE pFileNode; - return pRootHandler->Search(pRootHandler, pSearch, PtrFileSize, PtrLocaleFlags, PtrFileDataId); + pFileNode = FileTree.InsertByName(pCKeyEntry, szFileName, FileTree.GetNextFileDataId()); + return (pFileNode != NULL) ? ERROR_SUCCESS : ERROR_CAN_NOT_COMPLETE; } -void RootHandler_EndSearch(TRootHandler * pRootHandler, struct _TCascSearch * pSearch) +PCASC_CKEY_ENTRY TFileTreeRoot::GetFile(TCascStorage * /* hs */, const char * szFileName) { - // Check if the root structure is valid at all - if(pRootHandler != NULL) - { - pRootHandler->EndSearch(pRootHandler, pSearch); - } + PCASC_FILE_NODE pFileNode; + ULONGLONG FileNameHash = CalcFileNameHash(szFileName); + + pFileNode = FileTree.Find(FileNameHash); + return (pFileNode != NULL) ? pFileNode->pCKeyEntry : NULL; } -LPBYTE RootHandler_GetKey(TRootHandler * pRootHandler, const char * szFileName) +PCASC_CKEY_ENTRY TFileTreeRoot::GetFile(TCascStorage * /* hs */, DWORD FileDataId) { - // Check if the root structure is valid at all - if(pRootHandler == NULL) - return NULL; + PCASC_FILE_NODE pFileNode; - return pRootHandler->GetKey(pRootHandler, szFileName); + pFileNode = FileTree.FindById(FileDataId); + return (pFileNode != NULL) ? pFileNode->pCKeyEntry : NULL; } -void RootHandler_Dump(TCascStorage * hs, LPBYTE pbRootHandler, DWORD cbRootHandler, const TCHAR * szNameFormat, const TCHAR * szListFile, int nDumpLevel) +PCASC_CKEY_ENTRY TFileTreeRoot::Search(TCascSearch * pSearch, PCASC_FIND_DATA pFindData) { - TDumpContext * dc; + PCASC_FILE_NODE pFileNode; + size_t nMaxFileIndex = FileTree.GetMaxFileIndex(); - // Only if the ROOT provider suports the dump option - if(hs->pRootHandler != NULL && hs->pRootHandler->Dump != NULL) + // Are we still inside the root directory range? + while(pSearch->nFileIndex < nMaxFileIndex) { - // Create the dump file - dc = CreateDumpContext(hs, szNameFormat); - if(dc != NULL) + //BREAKIF(pSearch->nFileIndex >= 2823765); + + // Retrieve the file item + pFileNode = FileTree.PathAt(pFindData->szFileName, MAX_PATH, pSearch->nFileIndex++); + if(pFileNode != NULL) { - // Dump the content and close the file - hs->pRootHandler->Dump(hs, dc, pbRootHandler, cbRootHandler, szListFile, nDumpLevel); - dump_close(dc); + // Ignore folders and mount points + if(!(pFileNode->Flags & CFN_FLAG_FOLDER)) + { + // Check the wildcard + if (CascCheckWildCard(pFindData->szFileName, pSearch->szMask)) + { + // Retrieve the extra values (FileDataId, file size and locale flags) + FileTree.GetExtras(pFileNode, &pFindData->dwFileDataId, &pFindData->dwLocaleFlags, &pFindData->dwContentFlags); + + // Supply the bCanOpenByDataId variable + pFindData->bCanOpenByName = (pFileNode->FileNameHash != 0); + pFindData->bCanOpenByDataId = (pFindData->dwFileDataId != CASC_INVALID_ID); + + // Return the found CKey entry + return pFileNode->pCKeyEntry; + } + } } } + + // No more entries + return NULL; } -void RootHandler_Close(TRootHandler * pRootHandler) +bool TFileTreeRoot::GetInfo(PCASC_CKEY_ENTRY pCKeyEntry, PCASC_FILE_FULL_INFO pFileInfo) { - // Check if the root structure is allocated at all - if(pRootHandler != NULL) + PCASC_FILE_NODE pFileNode; + + // Can't do much if the root key is NULL + if(pCKeyEntry != NULL) { - pRootHandler->Close(pRootHandler); + pFileNode = FileTree.Find(pCKeyEntry); + if(pFileNode != NULL) + { + FileTree.GetExtras(pFileNode, &pFileInfo->FileDataId, &pFileInfo->LocaleFlags, &pFileInfo->ContentFlags); + pFileInfo->FileNameHash = pFileNode->FileNameHash; + return true; + } } -} - -DWORD RootHandler_GetFileId(TRootHandler * pRootHandler, const char * szFileName) -{ - // Check if the root structure is valid at all - if(pRootHandler == NULL) - return 0; - return pRootHandler->GetFileId(pRootHandler, szFileName); + return false; } diff --git a/dep/CascLib/src/common/RootHandler.h b/dep/CascLib/src/common/RootHandler.h index d1b66e501d8..4e56650b034 100644 --- a/dep/CascLib/src/common/RootHandler.h +++ b/dep/CascLib/src/common/RootHandler.h @@ -15,82 +15,97 @@ // Defines #define CASC_MNDX_ROOT_SIGNATURE 0x58444E4D // 'MNDX' +#define CASC_TVFS_ROOT_SIGNATURE 0x53465654 // 'TVFS' #define CASC_DIABLO3_ROOT_SIGNATURE 0x8007D0C4 -#define CASC_OVERWATCH_ROOT_SIGNATURE 0x35444D23 // '#MD5' +#define CASC_WOW82_ROOT_SIGNATURE 0x4D465354 // 'TSFM', WoW since 8.2 -#define ROOT_FLAG_HAS_NAMES 0x00000001 // The root file contains file names - -#define DUMP_LEVEL_ROOT_FILE 1 // Dump root file -#define DUMP_LEVEL_ENCODING_FILE 2 // Dump root file + encoding file -#define DUMP_LEVEL_INDEX_ENTRIES 3 // Dump root file + encoding file + index entries +#define DUMP_LEVEL_ROOT_FILE 1 // Dump root file +#define DUMP_LEVEL_ENCODING_FILE 2 // Dump root file + encoding file +#define DUMP_LEVEL_INDEX_ENTRIES 3 // Dump root file + encoding file + index entries //----------------------------------------------------------------------------- -// Root file function prototypes - -typedef int (*ROOT_INSERT)( - struct TRootHandler * pRootHandler, // Pointer to an initialized root handler - const char * szFileName, // Pointer to the file name - LPBYTE pbEncodingKey // Pointer to the encoding key of the file name - ); - -typedef LPBYTE (*ROOT_SEARCH)( - struct TRootHandler * pRootHandler, // Pointer to an initialized root handler - struct _TCascSearch * pSearch, // Pointer to the initialized search structure - PDWORD PtrFileSize, // Pointer to receive file size (optional) - PDWORD PtrLocaleFlags, // Pointer to receive locale flags (optional) - PDWORD PtrFileDataId // Pointer to FileDataID (optional) - ); - -typedef void (*ROOT_ENDSEARCH)( - struct TRootHandler * pRootHandler, // Pointer to an initialized root handler - struct _TCascSearch * pSearch // Pointer to the initialized search structure - ); - -typedef LPBYTE (*ROOT_GETKEY)( - struct TRootHandler * pRootHandler, // Pointer to an initialized root handler - const char * szFileName // Pointer to the name of a file - ); - -typedef void (*ROOT_DUMP)( - struct _TCascStorage * hs, // Pointer to the open storage - TDumpContext * dc, // Opened dump context - LPBYTE pbRootHandler, // Pointer to the loaded ROOT file - DWORD cbRootHandler, // Length of the loaded ROOT file - const TCHAR * szListFile, - int nDumpLevel - ); - -typedef void (*ROOT_CLOSE)( - struct TRootHandler * pRootHandler // Pointer to an initialized root handler - ); - -typedef DWORD(*ROOT_GETFILEID)( -struct TRootHandler * pRootHandler, // Pointer to an initialized root handler - const char * szFileName // Pointer to the name of a file - ); +// Class for generic root handler struct TRootHandler { - ROOT_INSERT Insert; // Inserts an existing file name - ROOT_SEARCH Search; // Performs the root file search - ROOT_ENDSEARCH EndSearch; // Performs cleanup after searching - ROOT_GETKEY GetKey; // Retrieves encoding key for a file name - ROOT_DUMP Dump; - ROOT_CLOSE Close; // Closing the root file - ROOT_GETFILEID GetFileId; // Returns File Id for a given Filename - - DWORD dwRootFlags; // Root flags - see the ROOT_FLAG_XXX + public: + + TRootHandler() + { + dwFeatures = 0; + } + + virtual ~TRootHandler() + {} + + // Inserts new file name to the root handler + // szFileName - Pointer to the file name + // pCKeyEntry - Pointer to the CASC_CKEY_ENTRY for the file + virtual int Insert(const char * /* szFileName */, PCASC_CKEY_ENTRY /* pCKeyEntry */) + { + return ERROR_NOT_SUPPORTED; + } + + // Searches the file by file name + // hs - Pointer to the storage structure + // szFileName - Pointer to the file name + virtual PCASC_CKEY_ENTRY GetFile(struct TCascStorage * /* hs */, const char * /* szFileName */) + { + return NULL; + } + + // Searches the file by file data id + // hs - Pointer to the storage structure + // FileDataId - File data id + virtual PCASC_CKEY_ENTRY GetFile(struct TCascStorage * /* hs */, DWORD /* FileDataId */) + { + return NULL; + } + + // Performs find-next-file operation + // pSearch - Pointer to the initialized search structure + // pFindData - Pointer to output structure that will contain the information + virtual PCASC_CKEY_ENTRY Search(struct TCascSearch * /* pSearch */, struct _CASC_FIND_DATA * /* pFindData */) + { + return NULL; + } + + // Returns advanced info from the root file entry. + // pCKeyEntry - CKey/EKey, depending on which type the root handler provides + // pFileInfo - Pointer to CASC_FILE_FULL_INFO structure + virtual bool GetInfo(PCASC_CKEY_ENTRY /* pCKeyEntry */, struct _CASC_FILE_FULL_INFO * /* pFileInfo */) + { + return false; + } + + DWORD GetFeatures() + { + return dwFeatures; + } + + protected: + + DWORD dwFeatures; // CASC features. See CASC_FEATURE_XXX }; //----------------------------------------------------------------------------- -// Public functions - -int RootHandler_Insert(TRootHandler * pRootHandler, const char * szFileName, LPBYTE pbEncodingKey); -LPBYTE RootHandler_Search(TRootHandler * pRootHandler, struct _TCascSearch * pSearch, PDWORD PtrFileSize, PDWORD PtrLocaleFlags, PDWORD PtrFileDataId); -void RootHandler_EndSearch(TRootHandler * pRootHandler, struct _TCascSearch * pSearch); -LPBYTE RootHandler_GetKey(TRootHandler * pRootHandler, const char * szFileName); -void RootHandler_Dump(struct _TCascStorage * hs, LPBYTE pbRootHandler, DWORD cbRootHandler, const TCHAR * szNameFormat, const TCHAR * szListFile, int nDumpLevel); -void RootHandler_Close(TRootHandler * pRootHandler); -DWORD RootHandler_GetFileId(TRootHandler * pRootHandler, const char * szFileName); +// Class for root handler that has basic mapping of FileName -> CASC_FILE_NODE + +struct TFileTreeRoot : public TRootHandler +{ + TFileTreeRoot(DWORD FileTreeFlags); + virtual ~TFileTreeRoot(); + + int Insert(const char * szFileName, PCASC_CKEY_ENTRY pCKeyEntry); + + PCASC_CKEY_ENTRY GetFile(struct TCascStorage * hs, const char * szFileName); + PCASC_CKEY_ENTRY GetFile(struct TCascStorage * hs, DWORD FileDataId); + PCASC_CKEY_ENTRY Search(struct TCascSearch * pSearch, struct _CASC_FIND_DATA * pFindData); + bool GetInfo(PCASC_CKEY_ENTRY pCKeyEntry, struct _CASC_FILE_FULL_INFO * pFileInfo); + + protected: + + CASC_FILE_TREE FileTree; +}; #endif // __ROOT_HANDLER_H__ |