mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2024-11-22 17:32:19 -05:00
e42b18cf71
* Fixed soh filters * add more makefile changes * almost ready * more updates * update * update * Update Makefiles to handle both platforms * Allow for overriding the CXX and CC executables * Restore original structure while supporting custom CXX flags * Remove some platform specific libs * Dynamic target name * Make X11 paths package-agnostic * Remove changes to `gfx_opengl.cpp` * Use OpenGL2 on MacOS instead of OpenGL3 * make it actually render something * render at least the first texture, still need to figure out the second one * Let’s use OpenGL 3 again * maybe this works to get the right texture? link's eyes still look off a bit * did this work? * set the platform to macos * actual numbers are right, but logic is ugly XXX/TODO, i know * add zlib to ldflags for ZAPDUtils * A bit of cleanup * Revert unneeded changes * Remove GL_CHECK * Fix issues with z64 branch * use an std::map instead of a giant array * three point filter fix (#2) * Fix mac compilation * fix audio for 64 bit * revert audio heap size, keep bigger pools * Add more Apple specific checks to our modifications * Add building instructions for macOS * Remove unecessary step from building instructions * Add missing SDL2 & GLEW to Linux LDLIBS * Update BUILDING.md Co-authored-by: BountyChocolate123456 <101743444+BountyChocolate123456@users.noreply.github.com> * Update soh/.gitignore to include other arch binaries Co-authored-by: BountyChocolate123456 <101743444+BountyChocolate123456@users.noreply.github.com> * Use right platform name for debugging window Co-authored-by: BountyChocolate123456 <101743444+BountyChocolate123456@users.noreply.github.com> * Fix stormlib on macos (arm64) * Simplify some of the ifdef checks * Revert an older no longer necessary fix * Remove remaining unecessary deviations * Update building instructions after StormLib changes * Feature: Use OpenGL 4.1 (#1) * Further tweak the BUILDING * Tidy up * reword -j message * Add Jenkins CI Support (#2) * Fix type issues * add target <appbundle> and <filledappbundle> add makefile targets to create an .app `filledappbundle` creates the target with the .otr included this should perhaps be moved to Application Support though * pull gcc's rpath from otool output * move make target to the end so it's not default * Add Jenkins and make exe in par with other platforms * Actually save build artefacts * Fix artefact path * Remove x11 mentions and linking (not used) * Update building instructions for generating app * use appsupport directory * Add new app icon * Update target to match macOS types * Update more audio types * fix null deref in Audio_PlayFanfare * Remove old import from z64 * address final nit with apple ifdefs Co-authored-by: KiritoDev <36680385+KiritoDv@users.noreply.github.com> Co-authored-by: Jeffrey Crowell <github@crowell.biz> Co-authored-by: BountyChocolate123456 <101743444+BountyChocolate123456@users.noreply.github.com>
1317 lines
45 KiB
C++
1317 lines
45 KiB
C++
/*****************************************************************************/
|
|
/* SFileAddFile.cpp Copyright (c) Ladislav Zezula 2010 */
|
|
/*---------------------------------------------------------------------------*/
|
|
/* MPQ Editing functions */
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Date Ver Who Comment */
|
|
/* -------- ---- --- ------- */
|
|
/* 27.03.10 1.00 Lad Splitted from SFileCreateArchiveEx.cpp */
|
|
/* 21.04.13 1.01 Dea AddFile callback now part of TMPQArchive */
|
|
/*****************************************************************************/
|
|
|
|
#define __STORMLIB_SELF__
|
|
#include "StormLib.h"
|
|
#include "StormCommon.h"
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Local variables
|
|
|
|
// Mask for lossy compressions
|
|
#define MPQ_LOSSY_COMPRESSION_MASK (MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_ADPCM_STEREO | MPQ_COMPRESSION_HUFFMANN)
|
|
|
|
// Data compression for SFileAddFile
|
|
// Kept here for compatibility with code that was created with StormLib version < 6.50
|
|
static DWORD DefaultDataCompression = MPQ_COMPRESSION_PKWARE;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// WAVE verification
|
|
|
|
#define FILE_SIGNATURE_RIFF 0x46464952
|
|
#define FILE_SIGNATURE_WAVE 0x45564157
|
|
#define FILE_SIGNATURE_FMT 0x20746D66
|
|
#define AUDIO_FORMAT_PCM 1
|
|
|
|
typedef struct _WAVE_FILE_HEADER
|
|
{
|
|
DWORD dwChunkId; // 0x52494646 ("RIFF")
|
|
DWORD dwChunkSize; // Size of that chunk, in bytes
|
|
DWORD dwFormat; // Must be 0x57415645 ("WAVE")
|
|
|
|
// Format sub-chunk
|
|
DWORD dwSubChunk1Id; // 0x666d7420 ("fmt ")
|
|
DWORD dwSubChunk1Size; // 0x16 for PCM
|
|
USHORT wAudioFormat; // 1 = PCM. Other value means some sort of compression
|
|
USHORT wChannels; // Number of channels
|
|
DWORD dwSampleRate; // 8000, 44100, etc.
|
|
DWORD dwBytesRate; // SampleRate * NumChannels * BitsPerSample/8
|
|
USHORT wBlockAlign; // NumChannels * BitsPerSample/8
|
|
USHORT wBitsPerSample; // 8 bits = 8, 16 bits = 16, etc.
|
|
|
|
// Followed by "data" sub-chunk (we don't care)
|
|
} WAVE_FILE_HEADER, *PWAVE_FILE_HEADER;
|
|
|
|
static bool IsWaveFile_16BitsPerAdpcmSample(
|
|
LPBYTE pbFileData,
|
|
DWORD cbFileData,
|
|
LPDWORD pdwChannels)
|
|
{
|
|
PWAVE_FILE_HEADER pWaveHdr = (PWAVE_FILE_HEADER)pbFileData;
|
|
|
|
// The amount of file data must be at least size of WAVE header
|
|
if(cbFileData > sizeof(WAVE_FILE_HEADER))
|
|
{
|
|
// Check for the RIFF header
|
|
if(pWaveHdr->dwChunkId == FILE_SIGNATURE_RIFF && pWaveHdr->dwFormat == FILE_SIGNATURE_WAVE)
|
|
{
|
|
// Check for ADPCM format
|
|
if(pWaveHdr->dwSubChunk1Id == FILE_SIGNATURE_FMT && pWaveHdr->wAudioFormat == AUDIO_FORMAT_PCM)
|
|
{
|
|
// Now the number of bits per sample must be at least 16.
|
|
// If not, the WAVE file gets corrupted by the ADPCM compression
|
|
if(pWaveHdr->wBitsPerSample >= 0x10)
|
|
{
|
|
*pdwChannels = pWaveHdr->wChannels;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static int FillWritableHandle(
|
|
TMPQArchive * ha,
|
|
TMPQFile * hf,
|
|
ULONGLONG FileTime,
|
|
DWORD dwFileSize,
|
|
DWORD dwFlags)
|
|
{
|
|
TFileEntry * pFileEntry = hf->pFileEntry;
|
|
|
|
// Initialize the hash entry for the file
|
|
hf->RawFilePos = ha->MpqPos + hf->MpqFilePos;
|
|
hf->dwDataSize = dwFileSize;
|
|
|
|
// Initialize the block table entry for the file
|
|
pFileEntry->ByteOffset = hf->MpqFilePos;
|
|
pFileEntry->dwFileSize = dwFileSize;
|
|
pFileEntry->dwCmpSize = 0;
|
|
pFileEntry->dwFlags = dwFlags | MPQ_FILE_EXISTS;
|
|
|
|
// Initialize the file time, CRC32 and MD5
|
|
//assert(sizeof(hf->hctx) >= sizeof(hash_state));
|
|
memset(pFileEntry->md5, 0, MD5_DIGEST_SIZE);
|
|
md5_init((hash_state *)hf->hctx);
|
|
pFileEntry->dwCrc32 = crc32(0, Z_NULL, 0);
|
|
|
|
// If the caller gave us a file time, use it.
|
|
pFileEntry->FileTime = FileTime;
|
|
|
|
// Mark the archive as modified
|
|
ha->dwFlags |= MPQ_FLAG_CHANGED;
|
|
|
|
// Call the callback, if needed
|
|
if(ha->pfnAddFileCB != NULL)
|
|
ha->pfnAddFileCB(ha->pvAddFileUserData, 0, hf->dwDataSize, false);
|
|
hf->dwAddFileError = ERROR_SUCCESS;
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// MPQ write data functions
|
|
|
|
static DWORD WriteDataToMpqFile(
|
|
TMPQArchive * ha,
|
|
TMPQFile * hf,
|
|
LPBYTE pbFileData,
|
|
DWORD dwDataSize,
|
|
DWORD dwCompression)
|
|
{
|
|
TFileEntry * pFileEntry = hf->pFileEntry;
|
|
ULONGLONG ByteOffset;
|
|
LPBYTE pbCompressed = NULL; // Compressed (target) data
|
|
LPBYTE pbToWrite = hf->pbFileSector; // Data to write to the file
|
|
DWORD dwErrCode = ERROR_SUCCESS;
|
|
int nCompressionLevel; // ADPCM compression level (only used for wave files)
|
|
|
|
// Make sure that the caller won't overrun the previously initiated file size
|
|
assert(hf->dwFilePos + dwDataSize <= pFileEntry->dwFileSize);
|
|
assert(hf->dwSectorCount != 0);
|
|
assert(hf->pbFileSector != NULL);
|
|
if((hf->dwFilePos + dwDataSize) > pFileEntry->dwFileSize)
|
|
return ERROR_DISK_FULL;
|
|
|
|
// Now write all data to the file sector buffer
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
DWORD dwBytesInSector = hf->dwFilePos % hf->dwSectorSize;
|
|
DWORD dwSectorIndex = hf->dwFilePos / hf->dwSectorSize;
|
|
DWORD dwBytesToCopy;
|
|
|
|
// Process all data.
|
|
while(dwDataSize != 0)
|
|
{
|
|
dwBytesToCopy = dwDataSize;
|
|
|
|
// Check for sector overflow
|
|
if(dwBytesToCopy > (hf->dwSectorSize - dwBytesInSector))
|
|
dwBytesToCopy = (hf->dwSectorSize - dwBytesInSector);
|
|
|
|
// Copy the data to the file sector
|
|
memcpy(hf->pbFileSector + dwBytesInSector, pbFileData, dwBytesToCopy);
|
|
dwBytesInSector += dwBytesToCopy;
|
|
pbFileData += dwBytesToCopy;
|
|
dwDataSize -= dwBytesToCopy;
|
|
|
|
// Update the file position
|
|
hf->dwFilePos += dwBytesToCopy;
|
|
|
|
// If the current sector is full, or if the file is already full,
|
|
// then write the data to the MPQ
|
|
if(dwBytesInSector >= hf->dwSectorSize || hf->dwFilePos >= pFileEntry->dwFileSize)
|
|
{
|
|
// Set the position in the file
|
|
ByteOffset = hf->RawFilePos + pFileEntry->dwCmpSize;
|
|
|
|
// Update CRC32 and MD5 of the file
|
|
md5_process((hash_state *)hf->hctx, hf->pbFileSector, dwBytesInSector);
|
|
hf->dwCrc32 = crc32(hf->dwCrc32, hf->pbFileSector, dwBytesInSector);
|
|
|
|
// Compress the file sector, if needed
|
|
if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK)
|
|
{
|
|
int nOutBuffer = (int)dwBytesInSector;
|
|
int nInBuffer = (int)dwBytesInSector;
|
|
|
|
// If the file is compressed, allocate buffer for the compressed data.
|
|
// Note that we allocate buffer that is a bit longer than sector size,
|
|
// for case if the compression method performs a buffer overrun
|
|
if(pbCompressed == NULL)
|
|
{
|
|
pbToWrite = pbCompressed = STORM_ALLOC(BYTE, hf->dwSectorSize + 0x100);
|
|
if(pbCompressed == NULL)
|
|
{
|
|
dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Note that both SCompImplode and SCompCompress copy data as-is,
|
|
// if they are unable to compress the data.
|
|
//
|
|
|
|
if(pFileEntry->dwFlags & MPQ_FILE_IMPLODE)
|
|
{
|
|
SCompImplode(pbCompressed, &nOutBuffer, hf->pbFileSector, nInBuffer);
|
|
}
|
|
|
|
if(pFileEntry->dwFlags & MPQ_FILE_COMPRESS)
|
|
{
|
|
// If this is the first sector, we need to override the given compression
|
|
// by the first sector compression. This is because the entire sector must
|
|
// be compressed by the same compression.
|
|
//
|
|
// Test case:
|
|
//
|
|
// WRITE_FILE(hFile, pvBuffer, 0x10, MPQ_COMPRESSION_PKWARE) // Write 0x10 bytes (sector 0)
|
|
// WRITE_FILE(hFile, pvBuffer, 0x10, MPQ_COMPRESSION_ADPCM_MONO) // Write 0x10 bytes (still sector 0)
|
|
// WRITE_FILE(hFile, pvBuffer, 0x10, MPQ_COMPRESSION_ADPCM_MONO) // Write 0x10 bytes (still sector 0)
|
|
// WRITE_FILE(hFile, pvBuffer, 0x10, MPQ_COMPRESSION_ADPCM_MONO) // Write 0x10 bytes (still sector 0)
|
|
dwCompression = (dwSectorIndex == 0) ? hf->dwCompression0 : dwCompression;
|
|
|
|
// If the caller wants ADPCM compression, we will set wave compression level to 4,
|
|
// which corresponds to medium quality
|
|
nCompressionLevel = (dwCompression & MPQ_LOSSY_COMPRESSION_MASK) ? 4 : -1;
|
|
SCompCompress(pbCompressed, &nOutBuffer, hf->pbFileSector, nInBuffer, (unsigned)dwCompression, 0, nCompressionLevel);
|
|
}
|
|
|
|
// Update sector positions
|
|
dwBytesInSector = nOutBuffer;
|
|
if(hf->SectorOffsets != NULL)
|
|
hf->SectorOffsets[dwSectorIndex+1] = hf->SectorOffsets[dwSectorIndex] + dwBytesInSector;
|
|
|
|
// We have to calculate sector CRC, if enabled
|
|
if(hf->SectorChksums != NULL)
|
|
hf->SectorChksums[dwSectorIndex] = adler32(0, pbCompressed, nOutBuffer);
|
|
}
|
|
|
|
// Encrypt the sector, if necessary
|
|
if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED)
|
|
{
|
|
BSWAP_ARRAY32_UNSIGNED(pbToWrite, dwBytesInSector);
|
|
EncryptMpqBlock(pbToWrite, dwBytesInSector, hf->dwFileKey + dwSectorIndex);
|
|
BSWAP_ARRAY32_UNSIGNED(pbToWrite, dwBytesInSector);
|
|
}
|
|
|
|
// Write the file sector
|
|
if(!FileStream_Write(ha->pStream, &ByteOffset, pbToWrite, dwBytesInSector))
|
|
{
|
|
dwErrCode = GetLastError();
|
|
break;
|
|
}
|
|
|
|
// Call the compact callback, if any
|
|
if(ha->pfnAddFileCB != NULL)
|
|
ha->pfnAddFileCB(ha->pvAddFileUserData, hf->dwFilePos, hf->dwDataSize, false);
|
|
|
|
// Update the compressed file size
|
|
pFileEntry->dwCmpSize += dwBytesInSector;
|
|
dwBytesInSector = 0;
|
|
dwSectorIndex++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cleanup
|
|
if(pbCompressed != NULL)
|
|
STORM_FREE(pbCompressed);
|
|
return dwErrCode;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Recrypts file data for file renaming
|
|
|
|
static DWORD RecryptFileData(
|
|
TMPQArchive * ha,
|
|
TMPQFile * hf,
|
|
const char * szFileName,
|
|
const char * szNewFileName)
|
|
{
|
|
ULONGLONG RawFilePos;
|
|
TFileEntry * pFileEntry = hf->pFileEntry;
|
|
DWORD dwBytesToRecrypt = pFileEntry->dwCmpSize;
|
|
DWORD dwOldKey;
|
|
DWORD dwNewKey;
|
|
DWORD dwErrCode = ERROR_SUCCESS;
|
|
|
|
// The file must be encrypted
|
|
assert(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED);
|
|
|
|
// File decryption key is calculated from the plain name
|
|
szNewFileName = GetPlainFileName(szNewFileName);
|
|
szFileName = GetPlainFileName(szFileName);
|
|
|
|
// Calculate both file keys
|
|
dwOldKey = DecryptFileKey(szFileName, pFileEntry->ByteOffset, pFileEntry->dwFileSize, pFileEntry->dwFlags);
|
|
dwNewKey = DecryptFileKey(szNewFileName, pFileEntry->ByteOffset, pFileEntry->dwFileSize, pFileEntry->dwFlags);
|
|
|
|
// Incase the keys are equal, don't recrypt the file
|
|
if(dwNewKey == dwOldKey)
|
|
return ERROR_SUCCESS;
|
|
hf->dwFileKey = dwOldKey;
|
|
|
|
// Calculate the raw position of the file in the archive
|
|
hf->MpqFilePos = pFileEntry->ByteOffset;
|
|
hf->RawFilePos = ha->MpqPos + hf->MpqFilePos;
|
|
|
|
// Allocate buffer for file transfer
|
|
dwErrCode = AllocateSectorBuffer(hf);
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
return dwErrCode;
|
|
|
|
// Also allocate buffer for sector offsets
|
|
// Note: Don't load sector checksums, we don't need to recrypt them
|
|
dwErrCode = AllocateSectorOffsets(hf, true);
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
return dwErrCode;
|
|
|
|
// If we have sector offsets, recrypt these as well
|
|
if(hf->SectorOffsets != NULL)
|
|
{
|
|
// Allocate secondary buffer for sectors copy
|
|
DWORD * SectorOffsetsCopy = STORM_ALLOC(DWORD, hf->SectorOffsets[0] / sizeof(DWORD));
|
|
DWORD dwSectorOffsLen = hf->SectorOffsets[0];
|
|
|
|
if(SectorOffsetsCopy == NULL)
|
|
return ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
// Recrypt the array of sector offsets
|
|
memcpy(SectorOffsetsCopy, hf->SectorOffsets, dwSectorOffsLen);
|
|
EncryptMpqBlock(SectorOffsetsCopy, dwSectorOffsLen, dwNewKey - 1);
|
|
BSWAP_ARRAY32_UNSIGNED(SectorOffsetsCopy, dwSectorOffsLen);
|
|
|
|
// Write the recrypted array back
|
|
if(!FileStream_Write(ha->pStream, &hf->RawFilePos, SectorOffsetsCopy, dwSectorOffsLen))
|
|
dwErrCode = GetLastError();
|
|
STORM_FREE(SectorOffsetsCopy);
|
|
}
|
|
|
|
// Now we have to recrypt all file sectors. We do it without
|
|
// recompression, because recompression is not necessary in this case
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
for(DWORD dwSector = 0; dwSector < hf->dwSectorCount; dwSector++)
|
|
{
|
|
DWORD dwRawDataInSector = hf->dwSectorSize;
|
|
DWORD dwRawByteOffset = dwSector * hf->dwSectorSize;
|
|
|
|
// Last sector: If there is not enough bytes remaining in the file, cut the raw size
|
|
if(dwRawDataInSector > dwBytesToRecrypt)
|
|
dwRawDataInSector = dwBytesToRecrypt;
|
|
|
|
// Fix the raw data length if the file is compressed
|
|
if(hf->SectorOffsets != NULL)
|
|
{
|
|
dwRawDataInSector = hf->SectorOffsets[dwSector+1] - hf->SectorOffsets[dwSector];
|
|
dwRawByteOffset = hf->SectorOffsets[dwSector];
|
|
}
|
|
|
|
// Calculate the raw file offset of the file sector
|
|
RawFilePos = CalculateRawSectorOffset(hf, dwRawByteOffset);
|
|
|
|
// Read the file sector
|
|
if(!FileStream_Read(ha->pStream, &RawFilePos, hf->pbFileSector, dwRawDataInSector))
|
|
{
|
|
dwErrCode = GetLastError();
|
|
break;
|
|
}
|
|
|
|
// If necessary, re-encrypt the sector
|
|
// Note: Recompression is not necessary here. Unlike encryption,
|
|
// the compression does not depend on the position of the file in MPQ.
|
|
BSWAP_ARRAY32_UNSIGNED(hf->pbFileSector, dwRawDataInSector);
|
|
DecryptMpqBlock(hf->pbFileSector, dwRawDataInSector, dwOldKey + dwSector);
|
|
EncryptMpqBlock(hf->pbFileSector, dwRawDataInSector, dwNewKey + dwSector);
|
|
BSWAP_ARRAY32_UNSIGNED(hf->pbFileSector, dwRawDataInSector);
|
|
|
|
// Write the sector back
|
|
if(!FileStream_Write(ha->pStream, &RawFilePos, hf->pbFileSector, dwRawDataInSector))
|
|
{
|
|
dwErrCode = GetLastError();
|
|
break;
|
|
}
|
|
|
|
// Decrement number of bytes remaining
|
|
dwBytesToRecrypt -= hf->dwSectorSize;
|
|
}
|
|
}
|
|
|
|
return dwErrCode;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Internal support for MPQ modifications
|
|
|
|
DWORD SFileAddFile_Init(
|
|
TMPQArchive * ha,
|
|
const char * szFileName,
|
|
ULONGLONG FileTime,
|
|
DWORD dwFileSize,
|
|
LCID lcLocale,
|
|
DWORD dwFlags,
|
|
TMPQFile ** phf)
|
|
{
|
|
TFileEntry * pFileEntry = NULL;
|
|
TMPQFile * hf = NULL; // File structure for newly added file
|
|
DWORD dwHashIndex = HASH_ENTRY_FREE;
|
|
DWORD dwErrCode = ERROR_SUCCESS;
|
|
|
|
//
|
|
// Note: This is an internal function so no validity checks are done.
|
|
// It is the caller's responsibility to make sure that no invalid
|
|
// flags get to this point
|
|
//
|
|
|
|
// Sestor CRC is not allowed with single unit files
|
|
if(dwFlags & MPQ_FILE_SINGLE_UNIT)
|
|
dwFlags &= ~MPQ_FILE_SECTOR_CRC;
|
|
|
|
// Sector CRC is not allowed if the file is not compressed
|
|
if(!(dwFlags & MPQ_FILE_COMPRESS_MASK))
|
|
dwFlags &= ~MPQ_FILE_SECTOR_CRC;
|
|
|
|
// Fix Key is not allowed if the file is not enrypted
|
|
if(!(dwFlags & MPQ_FILE_ENCRYPTED))
|
|
dwFlags &= ~MPQ_FILE_FIX_KEY;
|
|
|
|
// If the MPQ is of version 3.0 or higher, we ignore file locale.
|
|
// This is because HET and BET tables have no known support for it
|
|
if(ha->pHeader->wFormatVersion >= MPQ_FORMAT_VERSION_3)
|
|
lcLocale = 0;
|
|
|
|
// Allocate the TMPQFile entry for newly added file
|
|
hf = CreateWritableHandle(ha, dwFileSize);
|
|
if(hf == NULL)
|
|
return false;
|
|
|
|
// Allocate file entry in the MPQ
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
// Check if the file already exists in the archive
|
|
pFileEntry = GetFileEntryExact(ha, szFileName, lcLocale, &dwHashIndex);
|
|
if(pFileEntry != NULL)
|
|
{
|
|
if(dwFlags & MPQ_FILE_REPLACEEXISTING)
|
|
InvalidateInternalFiles(ha);
|
|
else
|
|
dwErrCode = ERROR_ALREADY_EXISTS;
|
|
}
|
|
else
|
|
{
|
|
// Attempt to allocate new file entry
|
|
pFileEntry = AllocateFileEntry(ha, szFileName, lcLocale, &dwHashIndex);
|
|
if(pFileEntry != NULL)
|
|
InvalidateInternalFiles(ha);
|
|
else
|
|
dwErrCode = ERROR_DISK_FULL;
|
|
}
|
|
|
|
// Set the file entry to the file structure
|
|
hf->pFileEntry = pFileEntry;
|
|
}
|
|
|
|
// Prepare the pointer to hash table entry
|
|
if(dwErrCode == ERROR_SUCCESS && ha->pHashTable != NULL && dwHashIndex < ha->pHeader->dwHashTableSize)
|
|
{
|
|
hf->pHashEntry = ha->pHashTable + dwHashIndex;
|
|
hf->pHashEntry->lcLocale = (USHORT)lcLocale;
|
|
}
|
|
|
|
// Prepare the file key
|
|
if(dwErrCode == ERROR_SUCCESS && (dwFlags & MPQ_FILE_ENCRYPTED))
|
|
{
|
|
hf->dwFileKey = DecryptFileKey(szFileName, hf->MpqFilePos, dwFileSize, dwFlags);
|
|
if(hf->dwFileKey == 0)
|
|
dwErrCode = ERROR_UNKNOWN_FILE_KEY;
|
|
}
|
|
|
|
// Fill the file entry and TMPQFile structure
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
// At this point, the file name in the file entry must be set
|
|
assert(pFileEntry->szFileName != NULL);
|
|
assert(_stricmp(pFileEntry->szFileName, szFileName) == 0);
|
|
|
|
dwErrCode = FillWritableHandle(ha, hf, FileTime, dwFileSize, dwFlags);
|
|
}
|
|
|
|
// Free the file handle if failed
|
|
if(dwErrCode != ERROR_SUCCESS && hf != NULL)
|
|
FreeFileHandle(hf);
|
|
|
|
// Give the handle to the caller
|
|
*phf = hf;
|
|
return dwErrCode;
|
|
}
|
|
|
|
DWORD SFileAddFile_Init(
|
|
TMPQArchive * ha,
|
|
TMPQFile * hfSrc,
|
|
TMPQFile ** phf)
|
|
{
|
|
TFileEntry * pFileEntry = NULL;
|
|
TMPQFile * hf = NULL; // File structure for newly added file
|
|
ULONGLONG FileTime = hfSrc->pFileEntry->FileTime;
|
|
DWORD dwFileSize = hfSrc->pFileEntry->dwFileSize;
|
|
DWORD dwFlags = hfSrc->pFileEntry->dwFlags;
|
|
DWORD dwErrCode = ERROR_SUCCESS;
|
|
|
|
// Allocate the TMPQFile entry for newly added file
|
|
hf = CreateWritableHandle(ha, dwFileSize);
|
|
if(hf == NULL)
|
|
dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
|
|
// We need to keep the file entry index the same like in the source archive
|
|
// This is because multiple hash table entries can point to the same file entry
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
// Retrieve the file entry for the target file
|
|
pFileEntry = ha->pFileTable + (hfSrc->pFileEntry - hfSrc->ha->pFileTable);
|
|
|
|
// Copy all variables except file name
|
|
if((pFileEntry->dwFlags & MPQ_FILE_EXISTS) == 0)
|
|
{
|
|
pFileEntry[0] = hfSrc->pFileEntry[0];
|
|
pFileEntry->szFileName = NULL;
|
|
}
|
|
else
|
|
dwErrCode = ERROR_ALREADY_EXISTS;
|
|
|
|
// Set the file entry to the file structure
|
|
hf->pFileEntry = pFileEntry;
|
|
}
|
|
|
|
// Prepare the pointer to hash table entry
|
|
if(dwErrCode == ERROR_SUCCESS && ha->pHashTable != NULL && hfSrc->pHashEntry != NULL)
|
|
{
|
|
hf->dwHashIndex = (DWORD)(hfSrc->pHashEntry - hfSrc->ha->pHashTable);
|
|
hf->pHashEntry = ha->pHashTable + hf->dwHashIndex;
|
|
}
|
|
|
|
// Prepare the file key (copy from source file)
|
|
if(dwErrCode == ERROR_SUCCESS && (dwFlags & MPQ_FILE_ENCRYPTED))
|
|
{
|
|
hf->dwFileKey = hfSrc->dwFileKey;
|
|
if(hf->dwFileKey == 0)
|
|
dwErrCode = ERROR_UNKNOWN_FILE_KEY;
|
|
}
|
|
|
|
// Fill the file entry and TMPQFile structure
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
dwErrCode = FillWritableHandle(ha, hf, FileTime, dwFileSize, dwFlags);
|
|
}
|
|
|
|
// Free the file handle if failed
|
|
if(dwErrCode != ERROR_SUCCESS && hf != NULL)
|
|
FreeFileHandle(hf);
|
|
|
|
// Give the handle to the caller
|
|
*phf = hf;
|
|
return dwErrCode;
|
|
}
|
|
|
|
DWORD SFileAddFile_Write(TMPQFile * hf, const void * pvData, DWORD dwSize, DWORD dwCompression)
|
|
{
|
|
TMPQArchive * ha;
|
|
TFileEntry * pFileEntry;
|
|
DWORD dwErrCode = ERROR_SUCCESS;
|
|
|
|
// Don't bother if the caller gave us zero size
|
|
if(pvData == NULL || dwSize == 0)
|
|
return ERROR_SUCCESS;
|
|
|
|
// Get pointer to the MPQ archive
|
|
pFileEntry = hf->pFileEntry;
|
|
ha = hf->ha;
|
|
|
|
// Allocate file buffers
|
|
if(hf->pbFileSector == NULL)
|
|
{
|
|
ULONGLONG RawFilePos = hf->RawFilePos;
|
|
|
|
// Allocate buffer for file sector
|
|
hf->dwAddFileError = dwErrCode = AllocateSectorBuffer(hf);
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
return dwErrCode;
|
|
|
|
// Allocate patch info, if the data is patch
|
|
if(hf->pPatchInfo == NULL && IsIncrementalPatchFile(pvData, dwSize, &hf->dwPatchedFileSize))
|
|
{
|
|
// Set the MPQ_FILE_PATCH_FILE flag
|
|
pFileEntry->dwFlags |= MPQ_FILE_PATCH_FILE;
|
|
|
|
// Allocate the patch info
|
|
hf->dwAddFileError = dwErrCode = AllocatePatchInfo(hf, false);
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
return dwErrCode;
|
|
}
|
|
|
|
// Allocate sector offsets
|
|
if(hf->SectorOffsets == NULL)
|
|
{
|
|
hf->dwAddFileError = dwErrCode = AllocateSectorOffsets(hf, false);
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
return dwErrCode;
|
|
}
|
|
|
|
// Create array of sector checksums
|
|
if(hf->SectorChksums == NULL && (pFileEntry->dwFlags & MPQ_FILE_SECTOR_CRC))
|
|
{
|
|
hf->dwAddFileError = dwErrCode = AllocateSectorChecksums(hf, false);
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
return dwErrCode;
|
|
}
|
|
|
|
// Pre-save the patch info, if any
|
|
if(hf->pPatchInfo != NULL)
|
|
{
|
|
if(!FileStream_Write(ha->pStream, &RawFilePos, hf->pPatchInfo, hf->pPatchInfo->dwLength))
|
|
dwErrCode = GetLastError();
|
|
|
|
pFileEntry->dwCmpSize += hf->pPatchInfo->dwLength;
|
|
RawFilePos += hf->pPatchInfo->dwLength;
|
|
}
|
|
|
|
// Pre-save the sector offset table, just to reserve space in the file.
|
|
// Note that we dont need to swap the sector positions, nor encrypt the table
|
|
// at the moment, as it will be written again after writing all file sectors.
|
|
if(hf->SectorOffsets != NULL)
|
|
{
|
|
if(!FileStream_Write(ha->pStream, &RawFilePos, hf->SectorOffsets, hf->SectorOffsets[0]))
|
|
dwErrCode = GetLastError();
|
|
|
|
pFileEntry->dwCmpSize += hf->SectorOffsets[0];
|
|
RawFilePos += hf->SectorOffsets[0];
|
|
}
|
|
}
|
|
|
|
// Write the MPQ data to the file
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
// Save the first sector compression to the file structure
|
|
// Note that the entire first file sector will be compressed
|
|
// by compression that was passed to the first call of SFileAddFile_Write
|
|
if(hf->dwFilePos == 0)
|
|
hf->dwCompression0 = dwCompression;
|
|
|
|
// Write the data to the MPQ
|
|
dwErrCode = WriteDataToMpqFile(ha, hf, (LPBYTE)pvData, dwSize, dwCompression);
|
|
}
|
|
|
|
// If it succeeded and we wrote all the file data,
|
|
// we need to re-save sector offset table
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
if(hf->dwFilePos >= pFileEntry->dwFileSize)
|
|
{
|
|
// Finish calculating CRC32
|
|
pFileEntry->dwCrc32 = hf->dwCrc32;
|
|
|
|
// Finish calculating MD5
|
|
md5_done((hash_state *)hf->hctx, pFileEntry->md5);
|
|
|
|
// If we also have sector checksums, write them to the file
|
|
if(hf->SectorChksums != NULL)
|
|
{
|
|
dwErrCode = WriteSectorChecksums(hf);
|
|
}
|
|
|
|
// Now write patch info
|
|
if(hf->pPatchInfo != NULL)
|
|
{
|
|
memcpy(hf->pPatchInfo->md5, pFileEntry->md5, MD5_DIGEST_SIZE);
|
|
hf->pPatchInfo->dwDataSize = pFileEntry->dwFileSize;
|
|
pFileEntry->dwFileSize = hf->dwPatchedFileSize;
|
|
dwErrCode = WritePatchInfo(hf);
|
|
}
|
|
|
|
// Now write sector offsets to the file
|
|
if(hf->SectorOffsets != NULL)
|
|
{
|
|
dwErrCode = WriteSectorOffsets(hf);
|
|
}
|
|
|
|
// Write the MD5 hashes of each file chunk, if required
|
|
if(ha->pHeader->dwRawChunkSize != 0)
|
|
{
|
|
dwErrCode = WriteMpqDataMD5(ha->pStream,
|
|
ha->MpqPos + pFileEntry->ByteOffset,
|
|
hf->pFileEntry->dwCmpSize,
|
|
ha->pHeader->dwRawChunkSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update the archive size
|
|
if((ha->MpqPos + pFileEntry->ByteOffset + pFileEntry->dwCmpSize) > ha->FileSize)
|
|
ha->FileSize = ha->MpqPos + pFileEntry->ByteOffset + pFileEntry->dwCmpSize;
|
|
|
|
// Store the error code from the Write File operation
|
|
hf->dwAddFileError = dwErrCode;
|
|
return dwErrCode;
|
|
}
|
|
|
|
DWORD SFileAddFile_Finish(TMPQFile * hf)
|
|
{
|
|
TMPQArchive * ha = hf->ha;
|
|
TFileEntry * pFileEntry = hf->pFileEntry;
|
|
DWORD dwErrCode = hf->dwAddFileError;
|
|
|
|
// If all previous operations succeeded, we can update the MPQ
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
// Verify if the caller wrote the file properly
|
|
if(hf->pPatchInfo == NULL)
|
|
{
|
|
assert(pFileEntry != NULL);
|
|
if(hf->dwFilePos != pFileEntry->dwFileSize)
|
|
dwErrCode = ERROR_CAN_NOT_COMPLETE;
|
|
}
|
|
else
|
|
{
|
|
if(hf->dwFilePos != hf->pPatchInfo->dwDataSize)
|
|
dwErrCode = ERROR_CAN_NOT_COMPLETE;
|
|
}
|
|
}
|
|
|
|
// Now we need to recreate the HET table, if exists
|
|
if(dwErrCode == ERROR_SUCCESS && ha->pHetTable != NULL)
|
|
{
|
|
dwErrCode = RebuildHetTable(ha);
|
|
}
|
|
|
|
// Update the block table size
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
// Call the user callback, if any
|
|
if(ha->pfnAddFileCB != NULL)
|
|
ha->pfnAddFileCB(ha->pvAddFileUserData, hf->dwDataSize, hf->dwDataSize, true);
|
|
}
|
|
else
|
|
{
|
|
// Free the file entry in MPQ tables
|
|
if(pFileEntry != NULL)
|
|
DeleteFileEntry(ha, hf);
|
|
}
|
|
|
|
// Clear the add file callback
|
|
FreeFileHandle(hf);
|
|
return dwErrCode;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Adds data as file to the archive
|
|
|
|
bool WINAPI SFileCreateFile(
|
|
HANDLE hMpq,
|
|
const char * szArchivedName,
|
|
ULONGLONG FileTime,
|
|
DWORD dwFileSize,
|
|
LCID lcLocale,
|
|
DWORD dwFlags,
|
|
HANDLE * phFile)
|
|
{
|
|
TMPQArchive * ha = (TMPQArchive *)hMpq;
|
|
DWORD dwErrCode = ERROR_SUCCESS;
|
|
|
|
// Check valid parameters
|
|
if(!IsValidMpqHandle(hMpq))
|
|
dwErrCode = ERROR_INVALID_HANDLE;
|
|
if(szArchivedName == NULL || *szArchivedName == 0)
|
|
dwErrCode = ERROR_INVALID_PARAMETER;
|
|
if(phFile == NULL)
|
|
dwErrCode = ERROR_INVALID_PARAMETER;
|
|
|
|
// Don't allow to add file if the MPQ is open for read only
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
if(ha->dwFlags & MPQ_FLAG_READ_ONLY)
|
|
dwErrCode = ERROR_ACCESS_DENIED;
|
|
|
|
// Don't allow to add a file under pseudo-file name
|
|
if(IsPseudoFileName(szArchivedName, NULL))
|
|
dwErrCode = ERROR_INVALID_PARAMETER;
|
|
|
|
// Don't allow to add any of the internal files
|
|
if(IsInternalMpqFileName(szArchivedName))
|
|
dwErrCode = ERROR_INTERNAL_FILE;
|
|
}
|
|
|
|
// Perform validity check of the MPQ flags
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
// Mask all unsupported flags out
|
|
dwFlags &= (ha->dwFlags & MPQ_FLAG_WAR3_MAP) ? MPQ_FILE_VALID_FLAGS_W3X : MPQ_FILE_VALID_FLAGS;
|
|
|
|
// Check for valid flag combinations
|
|
if((dwFlags & (MPQ_FILE_IMPLODE | MPQ_FILE_COMPRESS)) == (MPQ_FILE_IMPLODE | MPQ_FILE_COMPRESS))
|
|
dwErrCode = ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
// Initiate the add file operation
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
dwErrCode = SFileAddFile_Init(ha, szArchivedName, FileTime, dwFileSize, lcLocale, dwFlags, (TMPQFile **)phFile);
|
|
|
|
// Deal with the errors
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
SetLastError(dwErrCode);
|
|
return (dwErrCode == ERROR_SUCCESS);
|
|
}
|
|
|
|
bool WINAPI SFileWriteFile(
|
|
HANDLE hFile,
|
|
const void * pvData,
|
|
DWORD dwSize,
|
|
DWORD dwCompression)
|
|
{
|
|
TMPQFile * hf = (TMPQFile *)hFile;
|
|
DWORD dwErrCode = ERROR_SUCCESS;
|
|
|
|
// Check the proper parameters
|
|
if(!IsValidFileHandle(hFile))
|
|
dwErrCode = ERROR_INVALID_HANDLE;
|
|
if(hf->bIsWriteHandle == false)
|
|
dwErrCode = ERROR_INVALID_HANDLE;
|
|
|
|
// Special checks for single unit files
|
|
if(dwErrCode == ERROR_SUCCESS && (hf->pFileEntry->dwFlags & MPQ_FILE_SINGLE_UNIT))
|
|
{
|
|
//
|
|
// Note: Blizzard doesn't support single unit files
|
|
// that are stored as encrypted or imploded. We will allow them here,
|
|
// the calling application must ensure that such flag combination doesn't get here
|
|
//
|
|
|
|
// if(dwFlags & MPQ_FILE_IMPLODE)
|
|
// dwErrCode = ERROR_INVALID_PARAMETER;
|
|
//
|
|
// if(dwFlags & MPQ_FILE_ENCRYPTED)
|
|
// dwErrCode = ERROR_INVALID_PARAMETER;
|
|
|
|
// Lossy compression is not allowed on single unit files
|
|
if(dwCompression & MPQ_LOSSY_COMPRESSION_MASK)
|
|
dwErrCode = ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
|
|
// Write the data to the file
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
dwErrCode = SFileAddFile_Write(hf, pvData, dwSize, dwCompression);
|
|
|
|
// Deal with errors
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
SetLastError(dwErrCode);
|
|
return (dwErrCode == ERROR_SUCCESS);
|
|
}
|
|
|
|
bool WINAPI SFileFinishFile(HANDLE hFile)
|
|
{
|
|
TMPQFile * hf = (TMPQFile *)hFile;
|
|
DWORD dwErrCode = ERROR_SUCCESS;
|
|
|
|
// Check the proper parameters
|
|
if(!IsValidFileHandle(hFile))
|
|
dwErrCode = ERROR_INVALID_HANDLE;
|
|
if(hf->bIsWriteHandle == false)
|
|
dwErrCode = ERROR_INVALID_HANDLE;
|
|
|
|
// Finish the file
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
dwErrCode = SFileAddFile_Finish(hf);
|
|
|
|
// Deal with errors
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
SetLastError(dwErrCode);
|
|
return (dwErrCode == ERROR_SUCCESS);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Adds a file to the archive
|
|
|
|
bool WINAPI SFileAddFileEx(
|
|
HANDLE hMpq,
|
|
const TCHAR * szFileName,
|
|
const char * szArchivedName,
|
|
DWORD dwFlags,
|
|
DWORD dwCompression, // Compression of the first sector
|
|
DWORD dwCompressionNext) // Compression of next sectors
|
|
{
|
|
ULONGLONG FileSize = 0;
|
|
ULONGLONG FileTime = 0;
|
|
TFileStream * pStream = NULL;
|
|
HANDLE hMpqFile = NULL;
|
|
LPBYTE pbFileData = NULL;
|
|
DWORD dwBytesRemaining = 0;
|
|
DWORD dwBytesToRead;
|
|
DWORD dwSectorSize = 0x1000;
|
|
DWORD dwChannels = 0;
|
|
bool bIsAdpcmCompression = false;
|
|
bool bIsFirstSector = true;
|
|
DWORD dwErrCode = ERROR_SUCCESS;
|
|
|
|
// Check parameters
|
|
if(hMpq == NULL || szFileName == NULL || *szFileName == 0)
|
|
{
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return false;
|
|
}
|
|
|
|
// Open added file
|
|
pStream = FileStream_OpenFile(szFileName, STREAM_FLAG_READ_ONLY | STREAM_PROVIDER_FLAT | BASE_PROVIDER_FILE);
|
|
if(pStream == NULL)
|
|
return false;
|
|
|
|
// Files bigger than 4GB cannot be added to MPQ
|
|
FileStream_GetTime(pStream, &FileTime);
|
|
FileStream_GetSize(pStream, &FileSize);
|
|
if(FileSize >> 32)
|
|
dwErrCode = ERROR_DISK_FULL;
|
|
|
|
// Allocate data buffer for reading from the source file
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
dwBytesRemaining = (DWORD)FileSize;
|
|
pbFileData = STORM_ALLOC(BYTE, dwSectorSize);
|
|
if(pbFileData == NULL)
|
|
dwErrCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
|
|
// Deal with various combination of compressions
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
// When the compression for next blocks is set to default,
|
|
// we will copy the compression for the first sector
|
|
if(dwCompressionNext == MPQ_COMPRESSION_NEXT_SAME)
|
|
dwCompressionNext = dwCompression;
|
|
|
|
// If the caller wants ADPCM compression, we make sure
|
|
// that the first sector is not compressed with lossy compression
|
|
if(dwCompressionNext & (MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_ADPCM_STEREO))
|
|
{
|
|
// The compression of the first file sector must not be ADPCM
|
|
// in order not to corrupt the headers
|
|
if(dwCompression & (MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_ADPCM_STEREO))
|
|
dwCompression = MPQ_COMPRESSION_PKWARE;
|
|
|
|
// Remove both flag mono and stereo flags.
|
|
// They will be re-added according to WAVE type
|
|
dwCompressionNext &= ~(MPQ_COMPRESSION_ADPCM_MONO | MPQ_COMPRESSION_ADPCM_STEREO);
|
|
bIsAdpcmCompression = true;
|
|
}
|
|
|
|
// Initiate adding file to the MPQ
|
|
if(!SFileCreateFile(hMpq, szArchivedName, FileTime, (DWORD)FileSize, g_lcFileLocale, dwFlags, &hMpqFile))
|
|
dwErrCode = GetLastError();
|
|
}
|
|
|
|
// Write the file data to the MPQ
|
|
while(dwErrCode == ERROR_SUCCESS && dwBytesRemaining != 0)
|
|
{
|
|
// Get the number of bytes remaining in the source file
|
|
dwBytesToRead = dwBytesRemaining;
|
|
if(dwBytesToRead > dwSectorSize)
|
|
dwBytesToRead = dwSectorSize;
|
|
|
|
// Read data from the local file
|
|
if(!FileStream_Read(pStream, NULL, pbFileData, dwBytesToRead))
|
|
{
|
|
dwErrCode = GetLastError();
|
|
break;
|
|
}
|
|
|
|
// If the file being added is a WAVE file, we check number of channels
|
|
if(bIsFirstSector && bIsAdpcmCompression)
|
|
{
|
|
// The file must really be a WAVE file with at least 16 bits per sample,
|
|
// otherwise the ADPCM compression will corrupt it
|
|
if(IsWaveFile_16BitsPerAdpcmSample(pbFileData, dwBytesToRead, &dwChannels))
|
|
{
|
|
// Setup the compression of next sectors according to number of channels
|
|
dwCompressionNext |= (dwChannels == 1) ? MPQ_COMPRESSION_ADPCM_MONO : MPQ_COMPRESSION_ADPCM_STEREO;
|
|
}
|
|
else
|
|
{
|
|
// Setup the compression of next sectors to a lossless compression
|
|
dwCompressionNext = (dwCompression & MPQ_LOSSY_COMPRESSION_MASK) ? MPQ_COMPRESSION_PKWARE : dwCompression;
|
|
}
|
|
|
|
bIsFirstSector = false;
|
|
}
|
|
|
|
// Add the file sectors to the MPQ
|
|
if(!SFileWriteFile(hMpqFile, pbFileData, dwBytesToRead, dwCompression))
|
|
{
|
|
dwErrCode = GetLastError();
|
|
break;
|
|
}
|
|
|
|
// Set the next data compression
|
|
dwBytesRemaining -= dwBytesToRead;
|
|
dwCompression = dwCompressionNext;
|
|
}
|
|
|
|
// Finish the file writing
|
|
if(hMpqFile != NULL)
|
|
{
|
|
if(!SFileFinishFile(hMpqFile))
|
|
dwErrCode = GetLastError();
|
|
}
|
|
|
|
// Cleanup and exit
|
|
if(pbFileData != NULL)
|
|
STORM_FREE(pbFileData);
|
|
if(pStream != NULL)
|
|
FileStream_Close(pStream);
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
SetLastError(dwErrCode);
|
|
return (dwErrCode == ERROR_SUCCESS);
|
|
}
|
|
|
|
// Adds a data file into the archive
|
|
bool WINAPI SFileAddFile(HANDLE hMpq, const TCHAR * szFileName, const char * szArchivedName, DWORD dwFlags)
|
|
{
|
|
return SFileAddFileEx(hMpq,
|
|
szFileName,
|
|
szArchivedName,
|
|
dwFlags,
|
|
DefaultDataCompression,
|
|
DefaultDataCompression);
|
|
}
|
|
|
|
// Adds a WAVE file into the archive
|
|
bool WINAPI SFileAddWave(HANDLE hMpq, const TCHAR * szFileName, const char * szArchivedName, DWORD dwFlags, DWORD dwQuality)
|
|
{
|
|
DWORD dwCompression = 0;
|
|
|
|
//
|
|
// Note to wave compression level:
|
|
// The following conversion table applied:
|
|
// High quality: WaveCompressionLevel = -1
|
|
// Medium quality: WaveCompressionLevel = 4
|
|
// Low quality: WaveCompressionLevel = 2
|
|
//
|
|
// Starcraft files are packed as Mono (0x41) on medium quality.
|
|
// Because this compression is not used anymore, our compression functions
|
|
// will default to WaveCompressionLevel = 4 when using ADPCM compression
|
|
//
|
|
|
|
// Convert quality to data compression
|
|
switch(dwQuality)
|
|
{
|
|
case MPQ_WAVE_QUALITY_HIGH:
|
|
// WaveCompressionLevel = -1;
|
|
dwCompression = MPQ_COMPRESSION_PKWARE;
|
|
break;
|
|
|
|
case MPQ_WAVE_QUALITY_MEDIUM:
|
|
// WaveCompressionLevel = 4;
|
|
dwCompression = MPQ_COMPRESSION_ADPCM_STEREO | MPQ_COMPRESSION_HUFFMANN;
|
|
break;
|
|
|
|
case MPQ_WAVE_QUALITY_LOW:
|
|
// WaveCompressionLevel = 2;
|
|
dwCompression = MPQ_COMPRESSION_ADPCM_STEREO | MPQ_COMPRESSION_HUFFMANN;
|
|
break;
|
|
}
|
|
|
|
return SFileAddFileEx(hMpq,
|
|
szFileName,
|
|
szArchivedName,
|
|
dwFlags,
|
|
MPQ_COMPRESSION_PKWARE, // First sector should be compressed as data
|
|
dwCompression); // Next sectors should be compressed as WAVE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// bool SFileRemoveFile(HANDLE hMpq, char * szFileName)
|
|
//
|
|
// This function removes a file from the archive.
|
|
//
|
|
|
|
bool WINAPI SFileRemoveFile(HANDLE hMpq, const char * szFileName, DWORD dwSearchScope)
|
|
{
|
|
TMPQArchive * ha = IsValidMpqHandle(hMpq);
|
|
TMPQFile * hf = NULL;
|
|
DWORD dwErrCode = ERROR_SUCCESS;
|
|
|
|
// Keep compiler happy
|
|
dwSearchScope = dwSearchScope;
|
|
|
|
// Check the parameters
|
|
if(ha == NULL)
|
|
dwErrCode = ERROR_INVALID_HANDLE;
|
|
if(szFileName == NULL || *szFileName == 0)
|
|
dwErrCode = ERROR_INVALID_PARAMETER;
|
|
if(IsInternalMpqFileName(szFileName))
|
|
dwErrCode = ERROR_INTERNAL_FILE;
|
|
|
|
// Do not allow to remove files from read-only or patched MPQs
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
if((ha->dwFlags & MPQ_FLAG_READ_ONLY) || (ha->haPatch != NULL))
|
|
dwErrCode = ERROR_ACCESS_DENIED;
|
|
}
|
|
|
|
// If all checks have passed, we can delete the file from the MPQ
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
// Open the file from the MPQ
|
|
if(SFileOpenFileEx(hMpq, szFileName, SFILE_OPEN_BASE_FILE, (HANDLE *)&hf))
|
|
{
|
|
// Delete the file entry
|
|
dwErrCode = DeleteFileEntry(ha, hf);
|
|
FreeFileHandle(hf);
|
|
}
|
|
else
|
|
dwErrCode = GetLastError();
|
|
}
|
|
|
|
// If the file has been deleted, we need to invalidate
|
|
// the internal files and recreate HET table
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
// Invalidate the entries for internal files
|
|
// After we are done with MPQ changes, we need to re-create them anyway
|
|
InvalidateInternalFiles(ha);
|
|
|
|
//
|
|
// Don't rebuild HET table now; the file's flags indicate
|
|
// that it's been deleted, which is enough
|
|
//
|
|
}
|
|
|
|
// Resolve error and exit
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
SetLastError(dwErrCode);
|
|
return (dwErrCode == ERROR_SUCCESS);
|
|
}
|
|
|
|
// Renames the file within the archive.
|
|
bool WINAPI SFileRenameFile(HANDLE hMpq, const char * szFileName, const char * szNewFileName)
|
|
{
|
|
TMPQArchive * ha = IsValidMpqHandle(hMpq);
|
|
TMPQFile * hf;
|
|
DWORD dwErrCode = ERROR_SUCCESS;
|
|
|
|
// Test the valid parameters
|
|
if(ha == NULL)
|
|
dwErrCode = ERROR_INVALID_HANDLE;
|
|
if(szFileName == NULL || *szFileName == 0 || szNewFileName == NULL || *szNewFileName == 0)
|
|
dwErrCode = ERROR_INVALID_PARAMETER;
|
|
if(IsInternalMpqFileName(szFileName) || IsInternalMpqFileName(szNewFileName))
|
|
dwErrCode = ERROR_INTERNAL_FILE;
|
|
|
|
// Do not allow to rename files in MPQ open for read only
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
if(ha->dwFlags & MPQ_FLAG_READ_ONLY)
|
|
dwErrCode = ERROR_ACCESS_DENIED;
|
|
}
|
|
|
|
// Open the new file. If exists, we don't allow rename operation
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
if(GetFileEntryLocale(ha, szNewFileName, g_lcFileLocale) != NULL)
|
|
dwErrCode = ERROR_ALREADY_EXISTS;
|
|
}
|
|
|
|
// Open the file from the MPQ
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
// Attempt to open the file
|
|
if(SFileOpenFileEx(hMpq, szFileName, SFILE_OPEN_BASE_FILE, (HANDLE *)&hf))
|
|
{
|
|
ULONGLONG RawDataOffs;
|
|
TFileEntry * pFileEntry = hf->pFileEntry;
|
|
|
|
// Invalidate the entries for internal files
|
|
InvalidateInternalFiles(ha);
|
|
|
|
// Rename the file entry in the table
|
|
dwErrCode = RenameFileEntry(ha, hf, szNewFileName);
|
|
|
|
// If the file is encrypted, we have to re-crypt the file content
|
|
// with the new decryption key
|
|
if((dwErrCode == ERROR_SUCCESS) && (pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED))
|
|
{
|
|
// Recrypt the file data in the MPQ
|
|
dwErrCode = RecryptFileData(ha, hf, szFileName, szNewFileName);
|
|
|
|
// Update the MD5 of the raw block
|
|
if(dwErrCode == ERROR_SUCCESS && ha->pHeader->dwRawChunkSize != 0)
|
|
{
|
|
RawDataOffs = ha->MpqPos + pFileEntry->ByteOffset;
|
|
WriteMpqDataMD5(ha->pStream,
|
|
RawDataOffs,
|
|
pFileEntry->dwCmpSize,
|
|
ha->pHeader->dwRawChunkSize);
|
|
}
|
|
}
|
|
|
|
// Free the file handle
|
|
FreeFileHandle(hf);
|
|
}
|
|
else
|
|
{
|
|
dwErrCode = GetLastError();
|
|
}
|
|
}
|
|
|
|
// We also need to rebuild the HET table, if present
|
|
if(dwErrCode == ERROR_SUCCESS && ha->pHetTable != NULL)
|
|
dwErrCode = RebuildHetTable(ha);
|
|
|
|
// Resolve error and exit
|
|
if(dwErrCode != ERROR_SUCCESS)
|
|
SetLastError(dwErrCode);
|
|
return (dwErrCode == ERROR_SUCCESS);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Sets default data compression for SFileAddFile
|
|
|
|
bool WINAPI SFileSetDataCompression(DWORD DataCompression)
|
|
{
|
|
unsigned int uValidMask = (MPQ_COMPRESSION_ZLIB | MPQ_COMPRESSION_PKWARE | MPQ_COMPRESSION_BZIP2 | MPQ_COMPRESSION_SPARSE);
|
|
|
|
if((DataCompression & uValidMask) != DataCompression)
|
|
{
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return false;
|
|
}
|
|
|
|
DefaultDataCompression = DataCompression;
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Changes locale ID of a file
|
|
|
|
bool WINAPI SFileSetFileLocale(HANDLE hFile, LCID lcNewLocale)
|
|
{
|
|
TMPQArchive * ha;
|
|
TFileEntry * pFileEntry;
|
|
TMPQFile * hf = IsValidFileHandle(hFile);
|
|
|
|
// Invalid handle => do nothing
|
|
if(hf == NULL)
|
|
{
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
return false;
|
|
}
|
|
|
|
// Do not allow to rename files in MPQ open for read only
|
|
ha = hf->ha;
|
|
if(ha->dwFlags & MPQ_FLAG_READ_ONLY)
|
|
{
|
|
SetLastError(ERROR_ACCESS_DENIED);
|
|
return false;
|
|
}
|
|
|
|
// Do not allow unnamed access
|
|
if(hf->pFileEntry->szFileName == NULL)
|
|
{
|
|
SetLastError(ERROR_CAN_NOT_COMPLETE);
|
|
return false;
|
|
}
|
|
|
|
// Do not allow to change locale of any internal file
|
|
if(IsInternalMpqFileName(hf->pFileEntry->szFileName))
|
|
{
|
|
SetLastError(ERROR_INTERNAL_FILE);
|
|
return false;
|
|
}
|
|
|
|
// Do not allow changing file locales if there is no hash table
|
|
if(hf->pHashEntry == NULL)
|
|
{
|
|
SetLastError(ERROR_NOT_SUPPORTED);
|
|
return false;
|
|
}
|
|
|
|
// We have to check if the file+locale is not already there
|
|
pFileEntry = GetFileEntryExact(ha, hf->pFileEntry->szFileName, lcNewLocale, NULL);
|
|
if(pFileEntry != NULL)
|
|
{
|
|
SetLastError(ERROR_ALREADY_EXISTS);
|
|
return false;
|
|
}
|
|
|
|
// Update the locale in the hash table entry
|
|
hf->pHashEntry->lcLocale = (USHORT)lcNewLocale;
|
|
ha->dwFlags |= MPQ_FLAG_CHANGED;
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Sets add file callback
|
|
|
|
bool WINAPI SFileSetAddFileCallback(HANDLE hMpq, SFILE_ADDFILE_CALLBACK AddFileCB, void * pvUserData)
|
|
{
|
|
TMPQArchive * ha = (TMPQArchive *) hMpq;
|
|
|
|
if(!IsValidMpqHandle(hMpq))
|
|
{
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
return false;
|
|
}
|
|
|
|
ha->pvAddFileUserData = pvUserData;
|
|
ha->pfnAddFileCB = AddFileCB;
|
|
return true;
|
|
}
|