/*****************************************************************************/
/* SFileGetFileInfo.cpp                   Copyright (c) Ladislav Zezula 2013 */
/*---------------------------------------------------------------------------*/
/* Description:                                                              */
/*---------------------------------------------------------------------------*/
/*   Date    Ver   Who  Comment                                              */
/* --------  ----  ---  -------                                              */
/* 30.11.13  1.00  Lad  The first version of SFileGetFileInfo.cpp            */
/*****************************************************************************/

#define __STORMLIB_SELF__
#include "StormLib.h"
#include "StormCommon.h"

//-----------------------------------------------------------------------------
// Local defines

// Information types for SFileGetFileInfo
#define SFILE_INFO_TYPE_INVALID_HANDLE  0
#define SFILE_INFO_TYPE_NOT_FOUND       1
#define SFILE_INFO_TYPE_DIRECT_POINTER  2
#define SFILE_INFO_TYPE_ALLOCATED       3
#define SFILE_INFO_TYPE_READ_FROM_FILE  4
#define SFILE_INFO_TYPE_TABLE_POINTER   5
#define SFILE_INFO_TYPE_FILE_ENTRY      6

//-----------------------------------------------------------------------------
// Local functions

static void ConvertFileEntryToSelfRelative(TFileEntry * pFileEntry, TFileEntry * pSrcFileEntry)
{
    // Copy the file entry itself
    memcpy(pFileEntry, pSrcFileEntry, sizeof(TFileEntry));

    // If source is NULL, leave it NULL
    if(pSrcFileEntry->szFileName != NULL)
    {
        // Set the file name pointer after the file entry
        pFileEntry->szFileName = (char *)(pFileEntry + 1);
        strcpy(pFileEntry->szFileName, pSrcFileEntry->szFileName);
    }
}


static DWORD GetMpqFileCount(TMPQArchive * ha)
{
    TFileEntry * pFileTableEnd;
    TFileEntry * pFileEntry;
    DWORD dwFileCount = 0;

    // Go through all open MPQs, including patches
    while(ha != NULL)
    {
        // Only count files that are not patch files
        pFileTableEnd = ha->pFileTable + ha->dwFileTableSize;
        for(pFileEntry = ha->pFileTable; pFileEntry < pFileTableEnd; pFileEntry++)
        {
            // If the file is patch file and this is not primary archive, skip it
            // BUGBUG: This errorneously counts non-patch files that are in both
            // base MPQ and in patches, and increases the number of files by cca 50%
            if((pFileEntry->dwFlags & (MPQ_FILE_EXISTS | MPQ_FILE_PATCH_FILE)) == MPQ_FILE_EXISTS)
                dwFileCount++;
        }

        // Move to the next patch archive
        ha = ha->haPatch;
    }

    return dwFileCount;
}

static bool GetFilePatchChain(TMPQFile * hf, void * pvFileInfo, DWORD cbFileInfo, DWORD * pcbLengthNeeded)
{
    TMPQFile * hfTemp;
    TCHAR * szFileInfo = (TCHAR *)pvFileInfo;
    size_t cchCharsNeeded = 1;
    size_t cchFileInfo = (cbFileInfo / sizeof(TCHAR));
    size_t nLength;

    // Patch chain is only supported on MPQ files.
    if(hf->pStream != NULL)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return false;
    }

    // Calculate the necessary length of the multi-string
    for(hfTemp = hf; hfTemp != NULL; hfTemp = hfTemp->hfPatch)
        cchCharsNeeded += _tcslen(FileStream_GetFileName(hfTemp->ha->pStream)) + 1;

    // Give the caller the needed length
    if(pcbLengthNeeded != NULL)
        pcbLengthNeeded[0] = (DWORD)(cchCharsNeeded * sizeof(TCHAR)); 

    // If the caller gave both buffer pointer and data length,
    // try to copy the patch chain
    if(szFileInfo != NULL && cchFileInfo != 0)
    {
        // If there is enough space in the buffer, copy the patch chain
        if(cchCharsNeeded > cchFileInfo)
        {
            SetLastError(ERROR_INSUFFICIENT_BUFFER);
            return false;
        }

        // Copy each patch
        for(hfTemp = hf; hfTemp != NULL; hfTemp = hfTemp->hfPatch)
        {
            // Get the file name and its length
            const TCHAR * szFileName = FileStream_GetFileName(hfTemp->ha->pStream);
            nLength = _tcslen(szFileName) + 1;

            // Copy the file name
            memcpy(szFileInfo, szFileName, nLength * sizeof(TCHAR));
            szFileInfo += nLength;
        }

        // Make it multi-string
        szFileInfo[0] = 0;
    }

    return true;
}

//-----------------------------------------------------------------------------
// Retrieves an information about an archive or about a file within the archive
//
//  hMpqOrFile - Handle to an MPQ archive or to a file
//  InfoClass  - Information to obtain
//  pvFileInfo - Pointer to buffer to store the information
//  cbFileInfo - Size of the buffer pointed by pvFileInfo
//  pcbLengthNeeded - Receives number of bytes necessary to store the information

bool WINAPI SFileGetFileInfo(
    HANDLE hMpqOrFile,
    SFileInfoClass InfoClass,
    void * pvFileInfo,
    DWORD cbFileInfo,
    LPDWORD pcbLengthNeeded)
{
    MPQ_SIGNATURE_INFO SignatureInfo;
    TMPQArchive * ha = NULL;
    TFileEntry * pFileEntry = NULL;
    ULONGLONG Int64Value = 0;
    ULONGLONG ByteOffset = 0;
    TMPQFile * hf = NULL;
    void * pvSrcFileInfo = NULL;
    DWORD cbSrcFileInfo = 0;
    DWORD dwInt32Value = 0;
    int nInfoType = SFILE_INFO_TYPE_INVALID_HANDLE;
    int nError = ERROR_SUCCESS;

    switch(InfoClass)
    {
        case SFileMpqFileName:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                pvSrcFileInfo = (void *)FileStream_GetFileName(ha->pStream);
                cbSrcFileInfo = (DWORD)(_tcslen((TCHAR *)pvSrcFileInfo) + 1) * sizeof(TCHAR);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileMpqStreamBitmap:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
                return FileStream_GetBitmap(ha->pStream, pvFileInfo, cbFileInfo, pcbLengthNeeded);
            break;

        case SFileMpqUserDataOffset:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                nInfoType = SFILE_INFO_TYPE_NOT_FOUND;
                if(ha->pUserData != NULL)
                {
                    pvSrcFileInfo = &ha->UserDataPos;
                    cbSrcFileInfo = sizeof(ULONGLONG);
                    nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
                }
            }
            break;

        case SFileMpqUserDataHeader:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                nInfoType = SFILE_INFO_TYPE_NOT_FOUND;
                if(ha->pUserData != NULL)
                {
                    ByteOffset = ha->UserDataPos;
                    cbSrcFileInfo = sizeof(TMPQUserData);
                    nInfoType = SFILE_INFO_TYPE_READ_FROM_FILE;
                }
            }
            break;

        case SFileMpqUserData:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                nInfoType = SFILE_INFO_TYPE_NOT_FOUND;
                if(ha->pUserData != NULL)
                {
                    ByteOffset = ha->UserDataPos + sizeof(TMPQUserData);
                    cbSrcFileInfo = ha->pUserData->dwHeaderOffs - sizeof(TMPQUserData);
                    nInfoType = SFILE_INFO_TYPE_READ_FROM_FILE;
                }
            }
            break;

        case SFileMpqHeaderOffset:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                pvSrcFileInfo = &ha->MpqPos;
                cbSrcFileInfo = sizeof(ULONGLONG);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileMpqHeaderSize:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                pvSrcFileInfo = &ha->pHeader->dwHeaderSize;
                cbSrcFileInfo = sizeof(DWORD);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileMpqHeader:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                ByteOffset = ha->MpqPos;
                cbSrcFileInfo = ha->pHeader->dwHeaderSize;
                nInfoType = SFILE_INFO_TYPE_READ_FROM_FILE;
            }
            break;

        case SFileMpqHetTableOffset:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                pvSrcFileInfo = &ha->pHeader->HetTablePos64;
                cbSrcFileInfo = sizeof(ULONGLONG);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileMpqHetTableSize:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                pvSrcFileInfo = &ha->pHeader->HetTableSize64;
                cbSrcFileInfo = sizeof(ULONGLONG);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileMpqHetHeader:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                nInfoType = SFILE_INFO_TYPE_NOT_FOUND;
                pvSrcFileInfo = LoadExtTable(ha, ha->pHeader->HetTablePos64, (size_t)ha->pHeader->HetTableSize64, HET_TABLE_SIGNATURE, MPQ_KEY_HASH_TABLE);
                if(pvSrcFileInfo != NULL)
                {
                    cbSrcFileInfo = sizeof(TMPQHetHeader);
                    nInfoType = SFILE_INFO_TYPE_ALLOCATED;
                }
            }
            break;

        case SFileMpqHetTable:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                nInfoType = SFILE_INFO_TYPE_NOT_FOUND;
                pvSrcFileInfo = LoadHetTable(ha);
                if(pvSrcFileInfo != NULL)
                {
                    cbSrcFileInfo = sizeof(void *);
                    nInfoType = SFILE_INFO_TYPE_TABLE_POINTER;
                }
            }
            break;

        case SFileMpqBetTableOffset:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                pvSrcFileInfo = &ha->pHeader->BetTablePos64;
                cbSrcFileInfo = sizeof(ULONGLONG);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileMpqBetTableSize:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                pvSrcFileInfo = &ha->pHeader->BetTableSize64;
                cbSrcFileInfo = sizeof(ULONGLONG);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileMpqBetHeader:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                nInfoType = SFILE_INFO_TYPE_NOT_FOUND;
                pvSrcFileInfo = LoadExtTable(ha, ha->pHeader->BetTablePos64, (size_t)ha->pHeader->BetTableSize64, BET_TABLE_SIGNATURE, MPQ_KEY_BLOCK_TABLE);
                if(pvSrcFileInfo != NULL)
                {
                    // It is allowed for the caller to only require BET header.
                    cbSrcFileInfo = sizeof(TMPQBetHeader) + ((TMPQBetHeader *)pvSrcFileInfo)->dwFlagCount * sizeof(DWORD);
                    if(cbFileInfo == sizeof(TMPQBetHeader))
                        cbSrcFileInfo = sizeof(TMPQBetHeader);
                    nInfoType = SFILE_INFO_TYPE_ALLOCATED;
                }
            }
            break;

        case SFileMpqBetTable:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                nInfoType = SFILE_INFO_TYPE_NOT_FOUND;
                pvSrcFileInfo = LoadBetTable(ha);
                if(pvSrcFileInfo != NULL)
                {
                    cbSrcFileInfo = sizeof(void *);
                    nInfoType = SFILE_INFO_TYPE_TABLE_POINTER;
                }
            }
            break;

        case SFileMpqHashTableOffset:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                Int64Value = MAKE_OFFSET64(ha->pHeader->wHashTablePosHi, ha->pHeader->dwHashTablePos);
                pvSrcFileInfo = &Int64Value;
                cbSrcFileInfo = sizeof(ULONGLONG);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileMpqHashTableSize64:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                pvSrcFileInfo = &ha->pHeader->HashTableSize64;
                cbSrcFileInfo = sizeof(ULONGLONG);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileMpqHashTableSize:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                pvSrcFileInfo = &ha->pHeader->dwHashTableSize;
                cbSrcFileInfo = sizeof(DWORD);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileMpqHashTable:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL && ha->pHashTable != NULL)
            {
                pvSrcFileInfo = ha->pHashTable;
                cbSrcFileInfo = ha->pHeader->dwHashTableSize * sizeof(TMPQHash);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileMpqBlockTableOffset:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                Int64Value = MAKE_OFFSET64(ha->pHeader->wBlockTablePosHi, ha->pHeader->dwBlockTablePos);
                pvSrcFileInfo = &Int64Value;
                cbSrcFileInfo = sizeof(ULONGLONG);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileMpqBlockTableSize64:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                pvSrcFileInfo = &ha->pHeader->BlockTableSize64;
                cbSrcFileInfo = sizeof(ULONGLONG);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileMpqBlockTableSize:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                pvSrcFileInfo = &ha->pHeader->dwBlockTableSize;
                cbSrcFileInfo = sizeof(DWORD);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileMpqBlockTable:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                nInfoType = SFILE_INFO_TYPE_NOT_FOUND;
                if(MAKE_OFFSET64(ha->pHeader->wBlockTablePosHi, ha->pHeader->dwBlockTablePos) != 0)
                {
                    cbSrcFileInfo = ha->pHeader->dwBlockTableSize * sizeof(TMPQBlock);
                    if(cbFileInfo >= cbSrcFileInfo)
                        pvSrcFileInfo = LoadBlockTable(ha, true);
                    nInfoType = SFILE_INFO_TYPE_ALLOCATED;
                }
            }
            break;

        case SFileMpqHiBlockTableOffset:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                pvSrcFileInfo = &ha->pHeader->HiBlockTablePos64;
                cbSrcFileInfo = sizeof(ULONGLONG);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileMpqHiBlockTableSize64:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                pvSrcFileInfo = &ha->pHeader->HiBlockTableSize64;
                cbSrcFileInfo = sizeof(ULONGLONG);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileMpqHiBlockTable:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                nInfoType = SFILE_INFO_TYPE_NOT_FOUND;
                if(ha->pHeader->HiBlockTablePos64 && ha->pHeader->HiBlockTableSize64)
                {
                    assert(false);
                }
            }
            break;

        case SFileMpqSignatures:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL && QueryMpqSignatureInfo(ha, &SignatureInfo))
            {
                pvSrcFileInfo = &SignatureInfo.SignatureTypes;
                cbSrcFileInfo = sizeof(DWORD);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileMpqStrongSignatureOffset:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                nInfoType = SFILE_INFO_TYPE_NOT_FOUND;
                if(QueryMpqSignatureInfo(ha, &SignatureInfo) && (SignatureInfo.SignatureTypes & SIGNATURE_TYPE_STRONG))
                {
                    pvSrcFileInfo = &SignatureInfo.EndMpqData;
                    cbSrcFileInfo = sizeof(ULONGLONG);
                    nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
                }
            }
            break;

        case SFileMpqStrongSignatureSize:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                nInfoType = SFILE_INFO_TYPE_NOT_FOUND;
                if(QueryMpqSignatureInfo(ha, &SignatureInfo) && (SignatureInfo.SignatureTypes & SIGNATURE_TYPE_STRONG))
                {
                    dwInt32Value = MPQ_STRONG_SIGNATURE_SIZE + 4;
                    pvSrcFileInfo = &dwInt32Value;
                    cbSrcFileInfo = sizeof(DWORD);
                    nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
                }
            }
            break;

        case SFileMpqStrongSignature:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                nInfoType = SFILE_INFO_TYPE_NOT_FOUND;
                if(QueryMpqSignatureInfo(ha, &SignatureInfo) && (SignatureInfo.SignatureTypes & SIGNATURE_TYPE_STRONG))
                {
                    pvSrcFileInfo = SignatureInfo.Signature;
                    cbSrcFileInfo = MPQ_STRONG_SIGNATURE_SIZE + 4;
                    nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
                }
            }
            break;

        case SFileMpqArchiveSize64:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                pvSrcFileInfo = &ha->pHeader->ArchiveSize64;
                cbSrcFileInfo = sizeof(DWORD);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileMpqArchiveSize:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                pvSrcFileInfo = &ha->pHeader->dwArchiveSize;
                cbSrcFileInfo = sizeof(DWORD);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileMpqMaxFileCount:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                pvSrcFileInfo = &ha->dwMaxFileCount;
                cbSrcFileInfo = sizeof(DWORD);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileMpqFileTableSize:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                pvSrcFileInfo = &ha->dwFileTableSize;
                cbSrcFileInfo = sizeof(DWORD);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileMpqSectorSize:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                pvSrcFileInfo = &ha->dwSectorSize;
                cbSrcFileInfo = sizeof(DWORD);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileMpqNumberOfFiles:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                pvSrcFileInfo = &dwInt32Value;
                cbSrcFileInfo = sizeof(DWORD);
                dwInt32Value = GetMpqFileCount(ha);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileMpqRawChunkSize:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                nInfoType = SFILE_INFO_TYPE_NOT_FOUND;
                if(ha->pHeader->dwRawChunkSize != 0)
                {
                    pvSrcFileInfo = &ha->pHeader->dwRawChunkSize;
                    cbSrcFileInfo = sizeof(DWORD);
                    nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
                }
            }
            break;

        case SFileMpqStreamFlags:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                FileStream_GetFlags(ha->pStream, &dwInt32Value);
                pvSrcFileInfo = &dwInt32Value;
                cbSrcFileInfo = sizeof(DWORD);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileMpqFlags:
            ha = IsValidMpqHandle(hMpqOrFile);
            if(ha != NULL)
            {
                dwInt32Value  = ha->dwFlags;
                pvSrcFileInfo = &dwInt32Value;
                cbSrcFileInfo = sizeof(DWORD);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileInfoPatchChain:
            hf = IsValidFileHandle(hMpqOrFile);
            if(hf != NULL)
                return GetFilePatchChain(hf, pvFileInfo, cbFileInfo, pcbLengthNeeded);
            break;

        case SFileInfoFileEntry:
            hf = IsValidFileHandle(hMpqOrFile);
            if(hf != NULL && hf->pFileEntry != NULL)
            {
                pvSrcFileInfo = pFileEntry = hf->pFileEntry;
                cbSrcFileInfo = sizeof(TFileEntry);
                if(pFileEntry->szFileName != NULL)
                    cbSrcFileInfo += (DWORD)strlen(pFileEntry->szFileName) + 1;
                nInfoType = SFILE_INFO_TYPE_FILE_ENTRY;
            }
            break;

        case SFileInfoHashEntry:
            hf = IsValidFileHandle(hMpqOrFile);
            if(hf != NULL && hf->pHashEntry != NULL)
            {
                pvSrcFileInfo = hf->pHashEntry;
                cbSrcFileInfo = sizeof(TMPQHash);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileInfoHashIndex:
            hf = IsValidFileHandle(hMpqOrFile);
            if(hf != NULL && hf->pHashEntry != NULL)
            {
                pvSrcFileInfo = &hf->dwHashIndex;
                cbSrcFileInfo = sizeof(DWORD);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileInfoNameHash1:
            hf = IsValidFileHandle(hMpqOrFile);
            if(hf != NULL && hf->pHashEntry != NULL)
            {
                dwInt32Value = hf->pHashEntry->dwName1;
                pvSrcFileInfo = &dwInt32Value;
                cbSrcFileInfo = sizeof(DWORD);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileInfoNameHash2:
            hf = IsValidFileHandle(hMpqOrFile);
            if(hf != NULL && hf->pHashEntry != NULL)
            {
                dwInt32Value = hf->pHashEntry->dwName2;
                pvSrcFileInfo = &dwInt32Value;
                cbSrcFileInfo = sizeof(DWORD);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileInfoNameHash3:
            hf = IsValidFileHandle(hMpqOrFile);
            if(hf != NULL && hf->pFileEntry != NULL)
            {
                pvSrcFileInfo = &hf->pFileEntry->FileNameHash;
                cbSrcFileInfo = sizeof(ULONGLONG);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileInfoLocale:
            hf = IsValidFileHandle(hMpqOrFile);
            if(hf != NULL && hf->pHashEntry != NULL)
            {
                dwInt32Value = hf->pHashEntry->lcLocale;
                pvSrcFileInfo = &dwInt32Value;
                cbSrcFileInfo = sizeof(DWORD);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileInfoFileIndex:
            hf = IsValidFileHandle(hMpqOrFile);
            if(hf != NULL && hf->ha != NULL && hf->pFileEntry != NULL)
            {
                dwInt32Value = (DWORD)(hf->pFileEntry - hf->ha->pFileTable);
                pvSrcFileInfo = &dwInt32Value;
                cbSrcFileInfo = sizeof(DWORD);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileInfoByteOffset:
            hf = IsValidFileHandle(hMpqOrFile);
            if(hf != NULL && hf->pFileEntry != NULL)
            {
                pvSrcFileInfo = &hf->pFileEntry->ByteOffset;
                cbSrcFileInfo = sizeof(ULONGLONG);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileInfoFileTime:
            hf = IsValidFileHandle(hMpqOrFile);
            if(hf != NULL && hf->pFileEntry != NULL)
            {
                pvSrcFileInfo = &hf->pFileEntry->FileTime;
                cbSrcFileInfo = sizeof(ULONGLONG);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileInfoFileSize:
            hf = IsValidFileHandle(hMpqOrFile);
            if(hf != NULL && hf->pFileEntry != NULL)
            {
                pvSrcFileInfo = &hf->pFileEntry->dwFileSize;
                cbSrcFileInfo = sizeof(DWORD);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileInfoCompressedSize:
            hf = IsValidFileHandle(hMpqOrFile);
            if(hf != NULL && hf->pFileEntry != NULL)
            {
                pvSrcFileInfo = &hf->pFileEntry->dwCmpSize;
                cbSrcFileInfo = sizeof(DWORD);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileInfoFlags:
            hf = IsValidFileHandle(hMpqOrFile);
            if(hf != NULL && hf->pFileEntry != NULL)
            {
                pvSrcFileInfo = &hf->pFileEntry->dwFlags;
                cbSrcFileInfo = sizeof(DWORD);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileInfoEncryptionKey:
            hf = IsValidFileHandle(hMpqOrFile);
            if(hf != NULL)
            {
                pvSrcFileInfo = &hf->dwFileKey;
                cbSrcFileInfo = sizeof(DWORD);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        case SFileInfoEncryptionKeyRaw:
            hf = IsValidFileHandle(hMpqOrFile);
            if(hf != NULL && hf->pFileEntry != NULL)
            {
                dwInt32Value = hf->dwFileKey;
                if(hf->pFileEntry->dwFlags & MPQ_FILE_FIX_KEY)
                    dwInt32Value = (dwInt32Value ^ hf->pFileEntry->dwFileSize) - (DWORD)hf->MpqFilePos;
                pvSrcFileInfo = &dwInt32Value;
                cbSrcFileInfo = sizeof(DWORD);
                nInfoType = SFILE_INFO_TYPE_DIRECT_POINTER;
            }
            break;

        default:    // Invalid info class
            SetLastError(ERROR_INVALID_PARAMETER);
            return false;
    }

    // If we validated the handle and info class, give as much info as possible
    if(nInfoType >= SFILE_INFO_TYPE_DIRECT_POINTER)
    {
        // Give the length needed, if wanted
        if(pcbLengthNeeded != NULL)
            pcbLengthNeeded[0] = cbSrcFileInfo;

        // If the caller entered an output buffer, the output size must also be entered
        if(pvFileInfo != NULL && cbFileInfo != 0)
        {
            // Check if there is enough space in the output buffer
            if(cbSrcFileInfo <= cbFileInfo)
            {
                switch(nInfoType)
                {
                    case SFILE_INFO_TYPE_DIRECT_POINTER:
                    case SFILE_INFO_TYPE_ALLOCATED:
                        assert(pvSrcFileInfo != NULL);
                        memcpy(pvFileInfo, pvSrcFileInfo, cbSrcFileInfo);
                        break;

                    case SFILE_INFO_TYPE_READ_FROM_FILE:
                        if(!FileStream_Read(ha->pStream, &ByteOffset, pvFileInfo, cbSrcFileInfo))
                            nError = GetLastError();
                        break;

                    case SFILE_INFO_TYPE_TABLE_POINTER:
                        assert(pvSrcFileInfo != NULL);
                        *(void **)pvFileInfo = pvSrcFileInfo;
                        pvSrcFileInfo = NULL;
                        break;

                    case SFILE_INFO_TYPE_FILE_ENTRY:
                        assert(pFileEntry != NULL);
                        ConvertFileEntryToSelfRelative((TFileEntry *)pvFileInfo, pFileEntry);
                        break;
                }
            }
            else
            {
                nError = ERROR_INSUFFICIENT_BUFFER;
            }
        }

        // Free the file info if needed
        if(nInfoType == SFILE_INFO_TYPE_ALLOCATED && pvSrcFileInfo != NULL)
            STORM_FREE(pvSrcFileInfo);
        if(nInfoType == SFILE_INFO_TYPE_TABLE_POINTER && pvSrcFileInfo != NULL)
            SFileFreeFileInfo(pvSrcFileInfo, InfoClass);
    }
    else
    {
        // Handle error cases
        if(nInfoType == SFILE_INFO_TYPE_INVALID_HANDLE)
            nError = ERROR_INVALID_HANDLE;
        if(nInfoType == SFILE_INFO_TYPE_NOT_FOUND)
            nError = ERROR_FILE_NOT_FOUND;
    }

    // Set the last error value, if needed
    if(nError != ERROR_SUCCESS)
        SetLastError(nError);
    return (nError == ERROR_SUCCESS);
}

bool WINAPI SFileFreeFileInfo(void * pvFileInfo, SFileInfoClass InfoClass)
{
    switch(InfoClass)
    {
        case SFileMpqHetTable:
            FreeHetTable((TMPQHetTable *)pvFileInfo);
            return true;

        case SFileMpqBetTable:
            FreeBetTable((TMPQBetTable *)pvFileInfo);
            return true;

        default:
            break;
    }

    SetLastError(ERROR_INVALID_PARAMETER);
    return false;
}

//-----------------------------------------------------------------------------
// Tries to retrieve the file name

struct TFileHeader2Ext
{
    DWORD dwOffset00Data;               // Required data at offset 00 (32-bits)
    DWORD dwOffset00Mask;               // Mask for data at offset 00 (32 bits). 0 = data are ignored
    DWORD dwOffset04Data;               // Required data at offset 04 (32-bits)
    DWORD dwOffset04Mask;               // Mask for data at offset 04 (32 bits). 0 = data are ignored
    const char * szExt;                 // Supplied extension, if the condition is true
};

static TFileHeader2Ext data2ext[] = 
{
    {0x00005A4D, 0x0000FFFF, 0x00000000, 0x00000000, "exe"},    // EXE files
    {0x00000006, 0xFFFFFFFF, 0x00000001, 0xFFFFFFFF, "dc6"},    // EXE files
    {0x1A51504D, 0xFFFFFFFF, 0x00000000, 0x00000000, "mpq"},    // MPQ archive header ID ('MPQ\x1A')
    {0x46464952, 0xFFFFFFFF, 0x00000000, 0x00000000, "wav"},    // WAVE header 'RIFF'
    {0x324B4D53, 0xFFFFFFFF, 0x00000000, 0x00000000, "smk"},    // Old "Smacker Video" files 'SMK2'
    {0x694B4942, 0xFFFFFFFF, 0x00000000, 0x00000000, "bik"},    // Bink video files (new)
    {0x0801050A, 0xFFFFFFFF, 0x00000000, 0x00000000, "pcx"},    // PCX images used in Diablo I
    {0x544E4F46, 0xFFFFFFFF, 0x00000000, 0x00000000, "fnt"},    // Font files used in Diablo II
    {0x6D74683C, 0xFFFFFFFF, 0x00000000, 0x00000000, "html"},   // HTML '<htm'
    {0x4D54483C, 0xFFFFFFFF, 0x00000000, 0x00000000, "html"},   // HTML '<HTM
    {0x216F6F57, 0xFFFFFFFF, 0x00000000, 0x00000000, "tbl"},    // Table files
    {0x31504C42, 0xFFFFFFFF, 0x00000000, 0x00000000, "blp"},    // BLP textures
    {0x32504C42, 0xFFFFFFFF, 0x00000000, 0x00000000, "blp"},    // BLP textures (v2)
    {0x584C444D, 0xFFFFFFFF, 0x00000000, 0x00000000, "mdx"},    // MDX files
    {0x45505954, 0xFFFFFFFF, 0x00000000, 0x00000000, "pud"},    // Warcraft II maps
    {0x38464947, 0xFFFFFFFF, 0x00000000, 0x00000000, "gif"},    // GIF images 'GIF8'
    {0x3032444D, 0xFFFFFFFF, 0x00000000, 0x00000000, "m2"},     // WoW ??? .m2
    {0x43424457, 0xFFFFFFFF, 0x00000000, 0x00000000, "dbc"},    // ??? .dbc
    {0x47585053, 0xFFFFFFFF, 0x00000000, 0x00000000, "bls"},    // WoW pixel shaders
    {0xE0FFD8FF, 0xFFFFFFFF, 0x00000000, 0x00000000, "jpg"},    // JPEG image
    {0x503B4449, 0xFFFFFFFF, 0x3B4C5857, 0xFFFFFFFF, "slk"},    // SLK file (usually starts with "ID;PWXL;N;E")
    {0x00000000, 0x00000000, 0x00000000, 0x00000000, "xxx"},    // Default extension
    {0, 0, 0, 0, NULL}                                          // Terminator 
};

static int CreatePseudoFileName(HANDLE hFile, TFileEntry * pFileEntry, char * szFileName)
{
    TMPQFile * hf = (TMPQFile *)hFile;  // MPQ File handle
    DWORD FirstBytes[2] = {0, 0};       // The first 4 bytes of the file
    DWORD dwBytesRead = 0;
    DWORD dwFilePos;                    // Saved file position

    // Read the first 2 DWORDs bytes from the file
    dwFilePos = SFileSetFilePointer(hFile, 0, NULL, FILE_CURRENT);   
    SFileReadFile(hFile, FirstBytes, sizeof(FirstBytes), &dwBytesRead, NULL);
    SFileSetFilePointer(hFile, dwFilePos, NULL, FILE_BEGIN);

    // If we read at least 8 bytes
    if(dwBytesRead == sizeof(FirstBytes))
    {
        // Make sure that the array is properly BSWAP-ed
        BSWAP_ARRAY32_UNSIGNED(FirstBytes, sizeof(FirstBytes));

        // Try to guess file extension from those 2 DWORDs
        for(size_t i = 0; data2ext[i].szExt != NULL; i++)
        {
            if((FirstBytes[0] & data2ext[i].dwOffset00Mask) == data2ext[i].dwOffset00Data &&
               (FirstBytes[1] & data2ext[i].dwOffset04Mask) == data2ext[i].dwOffset04Data)
            {
                char szPseudoName[20] = "";    

                // Format the pseudo-name
                sprintf(szPseudoName, "File%08u.%s", (unsigned int)(pFileEntry - hf->ha->pFileTable), data2ext[i].szExt);

                // Save the pseudo-name in the file entry as well
                AllocateFileName(hf->ha, pFileEntry, szPseudoName);

                // If the caller wants to copy the file name, do it
                if(szFileName != NULL)
                    strcpy(szFileName, szPseudoName);
                return ERROR_SUCCESS;
            }
        }
    }

    return ERROR_CAN_NOT_COMPLETE;
}

bool WINAPI SFileGetFileName(HANDLE hFile, char * szFileName)
{
    TMPQFile * hf = (TMPQFile *)hFile;  // MPQ File handle
    int nError = ERROR_INVALID_HANDLE;

    // Check valid parameters
    if(IsValidFileHandle(hFile))
    {
        TFileEntry * pFileEntry = hf->pFileEntry;

        // For MPQ files, retrieve the file name from the file entry
        if(hf->pStream == NULL)
        {
            if(pFileEntry != NULL)
            {
                // If the file name is not there yet, create a pseudo name
                if(pFileEntry->szFileName == NULL)
                    nError = CreatePseudoFileName(hFile, pFileEntry, szFileName);

                // Copy the file name to the output buffer, if any
                if(pFileEntry->szFileName && szFileName)
                {
                    strcpy(szFileName, pFileEntry->szFileName);
                    nError = ERROR_SUCCESS;
                }
            }
        }

        // For local files, copy the file name from the stream
        else
        {
            if(szFileName != NULL)
            {
                const TCHAR * szStreamName = FileStream_GetFileName(hf->pStream);
                CopyFileName(szFileName, szStreamName, _tcslen(szStreamName));
            }
            nError = ERROR_SUCCESS;
        }
    }

    if(nError != ERROR_SUCCESS)
        SetLastError(nError);
    return (nError == ERROR_SUCCESS);
}

