Files
Justin Marshall 101266ab72 Initial commit.
2026-04-27 10:35:40 -07:00

2643 lines
85 KiB
C++

/****************************************************************************
*
* SFILE.CPP
* Storm file I/O functions
*
* By Michael O'Brien (6/23/96)
*
***/
#include "pch.h"
#pragma hdrstop
#define CDROM_FALSE 0
#define CDROM_USEDFORDDA 1
#define CDROM_TRUE 2
#define FILL_UNREQUESTED 0
#define FILL_REQUESTED 1
#define FILL_PLAYING 2
#define FILL_CLOSING 3
#define HASH_INDEX 0
#define HASH_CHECK0 1
#define HASH_CHECK1 2
#define HASH_ENCRYPTKEY 3
#define HASH_ENCRYPTDATA 4
#define MPQ_COMPRESSED_PKWARE 0x00000100
#define MPQ_COMPRESSED_SCOMP 0x00000200
#define MPQ_COMPRESSEDMASK 0x0000FF00
#define MPQ_ENCRYPTED 0x00010000
#define MPQ_ENCRYPTED_FIXLOC 0x00020000
#define MPQ_ALLOCATED 0x80000000
#define RS_IDLE 0
#define RS_READING 1
#define RS_SEEKING 2
#define DEALLOCATED 0xFFFFFFFE
#define NOBLOCK 0xFFFFFFFF
#define SIGNATURE 0x1A51504D
#define DATACHUNKSIZE 0x20000
#define WAVECHUNKSIZE 0x4000
#define READAHEAD 0x1000
#define KEYCONTAINER "Blizzard_Storm"
#define SIGNATUREFILE "(signature)"
#define LISTFILE "(listfile)"
#define AUTHCOMPANIES 1
typedef struct _AUTHCOMPANYINFO {
LPCTSTR keyname;
DWORD authresult;
} AUTHCOMPANYINFO, *AUTHCOMPANYINFOPTR;
typedef struct _CRYPTOAPI {
BOOL (APIENTRY *CryptAcquireContext )(HCRYPTPROV *,LPCTSTR,LPCTSTR,DWORD,DWORD);
BOOL (APIENTRY *CryptCreateHash )(HCRYPTPROV,ALG_ID,HCRYPTKEY,DWORD,HCRYPTHASH *);
BOOL (APIENTRY *CryptDestroyHash )(HCRYPTHASH);
BOOL (APIENTRY *CryptDestroyKey )(HCRYPTKEY);
BOOL (APIENTRY *CryptHashData )(HCRYPTHASH,BYTE *,DWORD,DWORD);
BOOL (APIENTRY *CryptImportKey )(HCRYPTPROV,BYTE *,DWORD,HCRYPTKEY,DWORD,HCRYPTKEY *);
BOOL (APIENTRY *CryptReleaseContext )(HCRYPTPROV,DWORD);
BOOL (APIENTRY *CryptSignHash )(HCRYPTHASH,DWORD,LPCTSTR,DWORD,BYTE *,DWORD *);
BOOL (APIENTRY *CryptVerifySignature)(HCRYPTHASH,BYTE *,DWORD,HCRYPTKEY,LPCTSTR,DWORD);
} CRYPTOAPI, *CRYPTOAPIPTR;
typedef struct _SIGNATUREHEADER {
DWORD companyid;
DWORD reserved;
} SIGNATUREHEADER, *SIGNATUREHEADERPTR;
typedef struct _FILEHEADER {
DWORD signature;
DWORD headersize;
DWORD filesize;
WORD version;
WORD sectorsizeid;
DWORD hashoffset;
DWORD blockoffset;
DWORD hashcount;
DWORD blockcount;
} FILEHEADER, *FILEHEADERPTR;
typedef struct _HASHENTRY {
DWORD hashcheck[2];
LCID lcid;
DWORD block;
} HASHENTRY, *HASHENTRYPTR;
typedef struct _BLOCKENTRY {
DWORD offset;
DWORD sizealloc;
DWORD sizefile;
DWORD flags;
} BLOCKENTRY, *BLOCKENTRYPTR;
NODEDECL(ARCHIVEREC) {
char name[MAX_PATH];
HANDLE handle;
BOOL cdrom;
int priority;
LPVOID sectorfile;
DWORD sectorlocation;
DWORD sectorsize;
LPBYTE sectorbuffer;
DWORD sectorbytesread;
DWORD startinglocation;
BLOCKENTRYPTR blocktable;
FILEHEADERPTR fileheader;
HASHENTRYPTR hashtable;
DWORD lastlocation;
} *ARCHIVEPTR;
NODEDECL(FILEREC) {
char name[MAX_PATH];
HANDLE handle;
ARCHIVEPTR archive;
BLOCKENTRYPTR block;
DWORD key;
DWORD location;
DWORD lastlocation;
DWORD sectors;
LPDWORD sectoroffsettable;
BOOL sectoroffsettablevalid;
BOOL dda;
LPVOID readaheadbuffer;
DWORD readaheadoffset;
DWORD readaheadbytes;
} *FILEPTR;
typedef struct CKINFO {
DWORD size;
DWORD offset;
} CKINFO;
NODEDECL(AUDIOSTREAM) {
FILEPTR file;
DWORD nextwrite;
DWORD bytespersecond;
BOOL loop;
DWORD fillstatus;
DWORD bytespastend;
DWORD startinglocation;
DWORD totalsize;
LONG volume;
LONG pan;
LPDIRECTSOUNDBUFFER soundbuffer;
DWORD soundbuffersize;
BYTE fillvalue;
} *AUDIOSTREAMPTR;
NODEDECL(REQUEST) {
HANDLE event;
FILEPTR file;
DWORD location;
DWORD approxarchivelocation;
DWORD requiredcompletiontime;
LPVOID buffer;
LPDIRECTSOUNDBUFFER soundbuffer;
DWORD soundbufferoffset;
AUDIOSTREAMPTR stream;
DWORD bytestoread;
BOOL autodelrequest;
DWORD bytesread;
DWORD sequence;
DWORD dependentsequence;
} *REQUESTPTR;
static const AUTHCOMPANYINFO s_authcompany[AUTHCOMPANIES] = {{"BLIZZARDKEY",SFILE_AUTH_AUTHENTICBLIZZARD}};
static LIST(ARCHIVEREC) s_archivelist;
static char s_basepath[MAX_PATH] = "";
static HANDLE s_cdevent = INVALID_HANDLE_VALUE;
static LIST(REQUEST) s_cdreqlist;
static BOOL s_cdshutdown = 0;
static HANDLE s_cdthread = INVALID_HANDLE_VALUE;
static CCritSect s_critsect;
static LPDIRECTSOUND s_directsound = NULL;
static BOOL s_enabledirect = 0;
static LPVOID s_explodebuffer = NULL;
static LIST(FILEREC) s_filelist;
static LPDWORD s_hashsource = NULL;
static DWORD s_ioerrormode = SFILE_ERRORMODE_FATAL;
static SFILEERRORPROC s_ioerrorproc = NULL;
static LCID s_lcid = MAKELCID(MAKELANGID(LANG_NEUTRAL,SUBLANG_NEUTRAL),SORT_DEFAULT);
static LPVOID s_soundreadbuffer = NULL;
static LIST(AUDIOSTREAM) s_streamlist;
#ifdef _DEBUG
static CSLog s_log("Internal","SFile Trace File");
#define TRACEHANDLE s_log.GetHandle()
#define TRACEOUT SLogWrite
#else
#define TRACEHANDLE 0
#define TRACEOUT
#endif
/****************************************************************************
*
* DATA DECOMPRESSION (PKWARE) SUPPORT
*
***/
typedef struct _DECOMPRESSIONINFO {
LPVOID sourcebuffer;
DWORD sourceoffset;
LPVOID destbuffer;
DWORD destoffset;
DWORD bytes;
} DECOMPRESSIONINFO, *DECOMPRESSIONPTR;
//===========================================================================
static UINT __cdecl DecompressLzw_BufferRead (LPSTR buffer,
UINT *size,
LPVOID param) {
DECOMPRESSIONPTR infoptr = (DECOMPRESSIONPTR)param;
UINT bytes = min(*size,infoptr->bytes-infoptr->sourceoffset);
CopyMemory(buffer,(LPSTR)infoptr->sourcebuffer+infoptr->sourceoffset,bytes);
infoptr->sourceoffset += bytes;
return bytes;
}
//===========================================================================
static void __cdecl DecompressLzw_BufferWrite (LPSTR buffer,
UINT *size,
LPVOID param) {
DECOMPRESSIONPTR infoptr = (DECOMPRESSIONPTR)param;
CopyMemory((LPSTR)infoptr->destbuffer+infoptr->destoffset,buffer,*size);
infoptr->destoffset += *size;
}
//===========================================================================
static BOOL DecompressLzw (LPBYTE dest, LPBYTE source, DWORD sourcebytes) {
// CREATE A DECOMPRESSION BUFFER IF ONE DOES NOT ALREADY EXIST
if (!s_explodebuffer)
s_explodebuffer = ALLOC(EXP_BUFFER_SIZE);
// CREATE AN INFORMATION RECORD
DECOMPRESSIONINFO info;
info.sourcebuffer = source;
info.sourceoffset = 0;
info.destbuffer = dest;
info.destoffset = 0;
info.bytes = sourcebytes;
// PERFORM THE DECOMPRESSION
explode(DecompressLzw_BufferRead,
DecompressLzw_BufferWrite,
(LPSTR)s_explodebuffer,
&info);
return 1;
}
/****************************************************************************
*
* DATA DECRYPTION SUPPORT
*
***/
//===========================================================================
static void inline Decrypt (LPDWORD data, DWORD bytes, DWORD key) {
DWORD adjust = 0xEEEEEEEE;
DWORD iter = bytes >> 2;
while (iter--) {
adjust += *(s_hashsource+(HASH_ENCRYPTDATA << 8)+(key & 0xFF));
adjust += (*data++ ^= adjust+key)+(adjust << 5)+3;
key = (key >> 11) | ((key << 21) ^ 0xFFE00000)+0x11111111;
}
}
/****************************************************************************
*
* MOPAQ FILE PROCESSING FUNCTIONS
*
***/
static BOOL ReadFileChecked (HANDLE file,
LPVOID buffer,
DWORD bytestoread,
DWORD *bytesread,
LPOVERLAPPED overlapped,
LPCTSTR filename);
//===========================================================================
static DWORD inline Hash (LPCSTR filename, int hashtype) {
DWORD result = 0x7FED7FED;
DWORD adjust = 0xEEEEEEEE;
while (filename && *filename) {
char origchar = toupper(*filename++);
result = (result+adjust) ^ *(s_hashsource+(hashtype << 8)+origchar);
adjust += origchar+result+(adjust << 5)+3;
}
return result;
}
//===========================================================================
static DWORD inline Hash (LPCWSTR filename, int hashtype) {
DWORD result = 0x7FED7FED;
DWORD adjust = 0xEEEEEEEE;
while (filename && *filename) {
char origchar = toupper((char)((*filename++) & 0xFF));
result = (result+adjust) ^ *(s_hashsource+(hashtype << 8)+origchar);
adjust += origchar+result+(adjust << 5)+3;
}
return result;
}
//===========================================================================
static void inline InitializeHashSource () {
if (!s_hashsource)
return;
DWORD seed = 0x100001;
for (int loop1 = 0; loop1 < 256; ++loop1)
for (int loop2 = 0; loop2 < 5; ++loop2) {
seed = (seed*0x7D+3) % 0x2AAAAB;
DWORD rand1 = seed & 0xFFFF;
seed = (seed*0x7D+3) % 0x2AAAAB;
DWORD rand2 = seed & 0xFFFF;
*(s_hashsource+(loop2 << 8)+loop1) = (rand1 << 16) | rand2;
}
}
//===========================================================================
static DWORD InternalReadAligned (FILEPTR file,
DWORD location,
LPVOID buffer,
DWORD bytes) {
// ENSURE THAT THE READ IS SECTOR ALIGNED
VALIDATEBEGIN;
VALIDATE(!(location & (file->archive->sectorsize-1)));
VALIDATE(!(bytes & (file->archive->sectorsize-1)));
VALIDATEEND;
// CHECK FOR A NULL READ
if (!bytes)
return FALSE;
// IF THIS FILE IS COMPRESSED AND WE HAVEN'T YET READ THE SECTOR
// OFFSET TABLE, READ IT NOW
if ((file->block->flags & MPQ_COMPRESSEDMASK) &&
!file->sectoroffsettablevalid) {
if (file->block->offset != file->archive->lastlocation)
SetFilePointer(file->archive->handle,file->block->offset,NULL,FILE_BEGIN);
DWORD bytesread = 0;
ReadFileChecked(file->archive->handle,
file->sectoroffsettable,
(file->sectors+1)*sizeof(DWORD),
&bytesread,
NULL,
file->archive->name);
if (file->block->flags & MPQ_ENCRYPTED)
Decrypt((LPDWORD)file->sectoroffsettable,
bytesread,
file->key+0xFFFFFFFF);
file->sectoroffsettablevalid = TRUE;
file->archive->lastlocation = file->block->offset+bytesread;
}
// DETERMINE THE DISK LOCATION AND SIZE, GIVEN THE UNCOMPRESSED LOCATION
// AND SIZE
DWORD disklocation = location;
DWORD diskbytes = bytes;
if (file->block->flags & MPQ_COMPRESSEDMASK) {
disklocation = *(file->sectoroffsettable+location/file->archive->sectorsize);
diskbytes = *(file->sectoroffsettable+(location+bytes)/file->archive->sectorsize)
-disklocation;
}
// PROVIDE A SECONDARY BUFFER IF NECESSARY FOR DECOMPRESSION
LPVOID diskbuffer = buffer;
if (file->block->flags & MPQ_COMPRESSEDMASK)
diskbuffer = ALLOC(diskbytes+sizeof(DWORD));
// SET THE ARCHIVE FILE POINTER IF NECESSARY
DWORD archivelocation = file->block->offset+disklocation;
if (archivelocation != file->archive->lastlocation)
SetFilePointer(file->archive->handle,archivelocation,NULL,FILE_BEGIN);
// PERFORM THE READ
DWORD diskbytesread = 0;
ReadFileChecked(file->archive->handle,
diskbuffer,
diskbytes,
&diskbytesread,
NULL,
file->archive->name);
// SAVE THE NEW ARCHIVE FILE POINTER
file->archive->lastlocation = archivelocation+diskbytesread;
// IF WE WERE NOT ABLE TO READ THE ENTIRE REQUEST, DETERMINE A NUMBER OF
// UNCOMPRESSED BYTES THAT WE KNOW AT A MINIMUM WE READ SUCCESSFULLY
DWORD bytesread = bytes;
if (diskbytesread < diskbytes)
if (file->block->flags & MPQ_COMPRESSEDMASK) {
bytesread = 0;
DWORD checksector = location/file->archive->sectorsize+1;
while ((checksector <= file->sectors) &&
(diskbytesread >= (*(file->sectoroffsettable+checksector)-disklocation))) {
++checksector;
bytesread += file->archive->sectorsize;
}
}
else
bytesread = diskbytesread;
// DECRYPT THE DATA IF NECESSARY
if (file->block->flags & MPQ_ENCRYPTED) {
DWORD offset = 0;
DWORD sector = location/file->archive->sectorsize;
DWORD sectors = (bytesread+file->archive->sectorsize-1)/file->archive->sectorsize;
while ((sector < file->sectors) && sectors--) {
DWORD processbytes;
if (file->block->flags & MPQ_COMPRESSEDMASK)
processbytes = *(file->sectoroffsettable+sector+1)-*(file->sectoroffsettable+sector);
else {
processbytes = min(file->archive->sectorsize,bytesread-offset);
if ((sector == file->sectors-1) &&
(file->block->sizefile & (file->archive->sectorsize-1)))
processbytes = min(processbytes,file->block->sizefile & (file->archive->sectorsize-1));
}
Decrypt((LPDWORD)((LPBYTE)diskbuffer+offset),
processbytes & 0xFFFFFFFC,
file->key+sector);
++sector;
offset += processbytes;
}
}
// DECOMPRESS THE DATA IF NECESSARY
if (file->block->flags & MPQ_COMPRESSEDMASK) {
DWORD destoffset = 0;
DWORD sourceoffset = 0;
DWORD sector = location/file->archive->sectorsize;
DWORD sectors = (bytesread+file->archive->sectorsize-1)/file->archive->sectorsize;
while ((sector < file->sectors) && sectors--) {
DWORD sourcebytes = *(file->sectoroffsettable+sector+1)-*(file->sectoroffsettable+sector);
DWORD targetbytes = (sector == file->sectors-1)
? (file->block->sizefile && !(file->block->sizefile & (file->archive->sectorsize-1)))
? file->archive->sectorsize
: (file->block->sizefile & (file->archive->sectorsize-1))
: file->archive->sectorsize;
if (targetbytes > sourcebytes)
switch (file->block->flags & MPQ_COMPRESSEDMASK) {
case MPQ_COMPRESSED_PKWARE:
DecompressLzw((LPBYTE)buffer+destoffset,
(LPBYTE)diskbuffer+sourceoffset,
sourcebytes);
break;
case MPQ_COMPRESSED_SCOMP:
{
DWORD destsize = targetbytes;
SCompDecompress((LPBYTE)buffer+destoffset,
&destsize,
(LPBYTE)diskbuffer+sourceoffset,
sourcebytes);
}
break;
}
else if (diskbuffer != buffer)
CopyMemory((LPBYTE)buffer+destoffset,
(LPBYTE)diskbuffer+sourceoffset,
targetbytes);
destoffset += targetbytes;
sourceoffset += sourcebytes;
++sector;
}
}
// FREE THE DECOMPRESSION BUFFER IF NECESSARY
if (diskbuffer != buffer)
FREE(diskbuffer);
// RETURN THE NUMBER OF BYTES READ
return bytesread;
}
//===========================================================================
static DWORD InternalReadAlignedSector (FILEPTR file,
DWORD location) {
// IF THIS SECTOR IS ALREADY IN THE SECTOR BUFFER, JUST RETURN THE
// PREVIOUS RESULTS
if ((file->archive->sectorfile == file) &&
(file->archive->sectorlocation == location))
return file->archive->sectorbytesread;
// OTHERWISE, READ THE SECTOR
file->archive->sectorfile = file;
file->archive->sectorlocation = location;
file->archive->sectorbytesread = InternalReadAligned(file,
location,
file->archive->sectorbuffer,
file->archive->sectorsize);
return file->archive->sectorbytesread;
}
//===========================================================================
static DWORD InternalReadUnaligned (FILEPTR file,
DWORD location,
LPVOID buffer,
DWORD bytes) {
// CLIP THE READ SIZE AGAINST THE END OF THE FILE
if (file->block->sizefile <= location)
return 0;
else
bytes = min(bytes,file->block->sizefile-location);
// READ THE FIRST SECTOR
DWORD firstbytesread = 0;
if (location & (file->archive->sectorsize-1)) {
DWORD sectorbytesread = InternalReadAlignedSector(file,location & ~(file->archive->sectorsize-1));
DWORD unalignedbytes = min(bytes,file->archive->sectorsize-(location & (file->archive->sectorsize-1)));
CopyMemory(buffer,
file->archive->sectorbuffer+(location & (file->archive->sectorsize-1)),
unalignedbytes);
buffer = (LPBYTE)buffer+unalignedbytes;
bytes -= unalignedbytes;
if ((sectorbytesread != file->archive->sectorsize) || !bytes)
if (sectorbytesread < (location & (file->archive->sectorsize-1)))
return 0;
else
return min(unalignedbytes,sectorbytesread-(location & (file->archive->sectorsize-1)));
else
firstbytesread = unalignedbytes;
location += unalignedbytes;
}
// READ IN THE MIDDLE SECTORS
DWORD middlebytesread = 0;
if (bytes) {
DWORD middlebytes = bytes & ~(file->archive->sectorsize-1);
middlebytesread = InternalReadAligned(file,
location,
buffer,
bytes & ~(file->archive->sectorsize-1));
buffer = (LPBYTE)buffer+middlebytes;
bytes -= middlebytes;
location += middlebytes;
if ((middlebytes != middlebytesread) || !bytes)
return firstbytesread+middlebytesread;
}
// READ IN THE LAST SECTOR
DWORD lastbytesread = 0;
if (bytes) {
DWORD sectorbytesread = InternalReadAlignedSector(file,location);
CopyMemory(buffer,
file->archive->sectorbuffer,
bytes);
lastbytesread = min(bytes,sectorbytesread);
}
return firstbytesread+middlebytesread+lastbytesread;
}
//===========================================================================
static BOOL ReadFileChecked (HANDLE file,
LPVOID buffer,
DWORD bytestoread,
DWORD *bytesread,
LPOVERLAPPED overlapped,
LPCTSTR filename) {
for (;;) {
BOOL result = ReadFile(file,
buffer,
bytestoread,
bytesread,
overlapped);
if (result && (*bytesread == bytestoread))
return TRUE;
DWORD errorcode = GetLastError();
switch (s_ioerrormode) {
case SFILE_ERRORMODE_RETURNCODE:
return result;
case SFILE_ERRORMODE_CUSTOM:
if ((!s_ioerrorproc) ||
(!s_ioerrorproc(filename,errorcode)))
return result;
break;
case SFILE_ERRORMODE_FATAL:
SErrDisplayError(errorcode,
filename,
SERR_LINECODE_FILE,
NULL,
FALSE,
1);
break;
}
}
}
//===========================================================================
static DWORD inline SearchHashTable (ARCHIVEPTR archive, LPCTSTR filename, LCID lcid) {
DWORD hashindex = Hash(filename,HASH_INDEX);
DWORD hashcheck0 = Hash(filename,HASH_CHECK0);
DWORD hashcheck1 = Hash(filename,HASH_CHECK1);
DWORD entry = hashindex & (archive->fileheader->hashcount-1);
DWORD firstentry = entry;
DWORD found = 0xFFFFFFFF;
while ((archive->hashtable+entry)->block != NOBLOCK) {
if (((archive->hashtable+entry)->hashcheck[0] == hashcheck0) &&
((archive->hashtable+entry)->hashcheck[1] == hashcheck1) &&
((archive->hashtable+entry)->block != DEALLOCATED))
if ((archive->hashtable+entry)->lcid == lcid)
return entry;
else if ((archive->hashtable+entry)->lcid == MAKELCID(MAKELANGID(LANG_NEUTRAL,SUBLANG_NEUTRAL),SORT_DEFAULT))
found = entry;
entry = (entry+1) & (archive->fileheader->hashcount-1);
if (entry == firstentry)
break;
}
return found;
}
/****************************************************************************
*
* STREAMING AND OVERLAPPED I/O FUNCTIONS
*
***/
static REQUESTPTR IssueRequest (FILEPTR file,
DWORD locationoffset,
LPVOID buffer,
LPDIRECTSOUNDBUFFER soundbuffer,
DWORD soundbufferoffset,
AUDIOSTREAMPTR stream,
DWORD bytestoread,
DWORD requiredms,
DWORD dependentsequence,
HANDLE event,
BOOL autodelrequest,
BOOL triggerreadthread,
DWORD *sequence);
//===========================================================================
static inline BOOL CanProcessRequest (REQUESTPTR request) {
DWORD dependentsequence = request->dependentsequence;
if (dependentsequence)
ITERATELIST(REQUEST,s_cdreqlist,curr)
if (curr->sequence == dependentsequence)
return FALSE;
return TRUE;
}
//===========================================================================
static DWORD CALLBACK CdThreadProc (LPVOID) {
ARCHIVEPTR lastarchive = NULL;
DWORD lastarchivelocation = 0;
while (!s_cdshutdown) {
BOOL processed;
do {
processed = 0;
// IF THERE ARE ANY ACTIVE AUDIO STREAMS, ISSUE READ REQUESTS AS
// NECESSARY TO KEEP THEIR BUFFERS FULL
s_critsect.Enter();
{
AUDIOSTREAMPTR curr = s_streamlist.Head();
while (curr) {
// IF THIS STREAM HAS REACHED THE END OF THE WAVE FILE, EITHER LOOP
// OR WAIT UNTIL THE BUFFER IS DONE PLAYING BEFORE CLOSING THE
// STREAM
if ((curr->fillstatus == FILL_CLOSING) ||
((curr->fillstatus == FILL_PLAYING) &&
(curr->file->location >= curr->totalsize)))
if (curr->loop)
curr->file->location = curr->startinglocation;
else {
DWORD playcursor = 0;
DWORD writecursor = 0;
curr->soundbuffer->GetCurrentPosition(&playcursor,&writecursor);
if (((playcursor-curr->nextwrite) % curr->soundbuffersize) >= WAVECHUNKSIZE) {
DWORD sizeneeded = min(WAVECHUNKSIZE,curr->soundbuffersize-curr->nextwrite);
LPVOID buffer = NULL;
DWORD bytes = sizeneeded;
if (curr->soundbuffer->Lock(curr->nextwrite,
sizeneeded,
&buffer,
&bytes,
NULL,
NULL,
0) == DS_OK) {
FillMemory(buffer,bytes,curr->fillvalue);
curr->soundbuffer->Unlock(buffer,
bytes,
NULL,
0);
curr->bytespastend += bytes;
curr->nextwrite += bytes;
if (curr->nextwrite >= curr->soundbuffersize)
curr->nextwrite -= curr->soundbuffersize;
}
}
curr->fillstatus = FILL_CLOSING;
}
// IF THIS STREAM IS CURRENTLY PLAYING, CHECK WHETHER THERE IS ROOM
// TO LOAD ANOTHER CHUNK OF DATA
if (curr->fillstatus == FILL_PLAYING) {
DWORD playcursor = 0;
DWORD writecursor = 0;
curr->soundbuffer->GetCurrentPosition(&playcursor,&writecursor);
if (((playcursor-curr->nextwrite) % curr->soundbuffersize) >= WAVECHUNKSIZE) {
DWORD sizeneeded = min(WAVECHUNKSIZE,curr->soundbuffersize-curr->nextwrite);
sizeneeded = min(sizeneeded,curr->totalsize-curr->file->location);
IssueRequest(curr->file,
curr->file->location,
NULL,
curr->soundbuffer,
curr->nextwrite,
curr,
sizeneeded,
((curr->soundbuffersize-0x20000)*1000)/curr->bytespersecond,
0,
NULL,
TRUE,
FALSE,
NULL);
curr->file->location += sizeneeded;
curr->nextwrite += (curr->loop ? sizeneeded : WAVECHUNKSIZE);
if (curr->nextwrite >= curr->soundbuffersize)
curr->nextwrite -= curr->soundbuffersize;
}
}
// IF THIS STREAM IS NOT YET PLAYING, ISSUE THE INITIAL REQUESTS
// TO FILL THE BUFFER
else if (curr->fillstatus == FILL_UNREQUESTED) {
DWORD offset = 0;
DWORD maxoffset = min(curr->soundbuffersize,curr->totalsize);
DWORD timeoffset = 0;
while (offset < maxoffset) {
IssueRequest(curr->file,
curr->file->location,
NULL,
curr->soundbuffer,
offset,
curr,
min(WAVECHUNKSIZE,maxoffset-offset),
1000+(timeoffset += 500),
0,
NULL,
TRUE,
FALSE,
NULL);
curr->file->location += WAVECHUNKSIZE;
offset += WAVECHUNKSIZE;
}
curr->fillstatus = FILL_REQUESTED;
curr->nextwrite = offset % curr->soundbuffersize;
}
curr = curr->Next();
}
}
// FIND THE MOST URGENT REQUEST, AND THE REQUEST WHICH IS NEXT IN
// SEEK ORDER
REQUESTPTR nextreq = NULL;
REQUESTPTR urgentreq = NULL;
DWORD lowcompletetime = UINT_MAX;
{
DWORD lowseektime = UINT_MAX;
DWORD currtime = GetTickCount();
ITERATELIST(REQUEST,s_cdreqlist,curr)
if (CanProcessRequest(curr)) {
// CHECK THIS REQUEST FOR SEEK ORDER
{
DWORD adjlastlocation = 0;
if (lastarchivelocation > curr->file->archive->sectorsize)
adjlastlocation = lastarchivelocation-curr->file->archive->sectorsize;
DWORD seektime;
if (curr->file->archive == lastarchive)
if (curr->approxarchivelocation < adjlastlocation)
seektime = 0x8000000+curr->approxarchivelocation;
else
seektime = curr->approxarchivelocation-adjlastlocation;
else
seektime = UINT_MAX-1;
if (seektime <= lowseektime) {
lowseektime = seektime;
nextreq = curr;
}
}
// CHECK THIS REQUEST FOR URGENCY
{
DWORD completetime;
if (currtime-curr->requiredcompletiontime < 0x7FFFFFFF)
completetime = 0;
else
completetime = curr->requiredcompletiontime-currtime;
if (completetime <= lowcompletetime) {
lowcompletetime = completetime;
urgentreq = curr;
}
}
}
}
s_critsect.Leave();
// IF THE MOST URGENT REQUEST MUST COMPLETE IN THE NEXT
// 700 MILLISECONDS, PROCESS THAT REQUEST. OTHERWISE, PROCESS
// THE NEXT REQUEST IN SEEK ORDER
REQUESTPTR request = NULL;
if (urgentreq && (lowcompletetime <= 700))
request = urgentreq;
else if (nextreq)
request = nextreq;
// PERFORM THE REQUEST
if (request) {
// READ THE DATA FROM CD
lastarchive = request->file->archive;
lastarchivelocation = request->approxarchivelocation;
LPVOID readbuffer = request->buffer;
if (request->soundbuffer)
readbuffer = s_soundreadbuffer;
if (readbuffer)
request->bytesread = InternalReadUnaligned(request->file,
request->location,
readbuffer,
request->bytestoread);
else
request->bytesread = 0;
// IF THE DATA IS GOING INTO A SOUND BUFFER, LOCK THE SOUND BUFFER
// AND MOVE THE DATA
if (request->soundbuffer) {
request->soundbuffer->Lock(request->soundbufferoffset,
request->bytestoread,
&request->buffer,
&request->bytestoread,
NULL,
NULL,
0);
CopyMemory(request->buffer,readbuffer,request->bytesread);
DWORD fullbytes = min(WAVECHUNKSIZE,request->stream->soundbuffersize-request->soundbufferoffset);
if ((request->bytesread < fullbytes) && !request->stream->loop)
FillMemory((LPBYTE)request->buffer+request->bytesread,fullbytes-request->bytesread,request->stream->fillvalue);
request->soundbuffer->Unlock(request->buffer,
request->bytestoread,
NULL,
0);
if ((request->stream->fillstatus == FILL_REQUESTED) &&
((request->location+request->bytestoread >= request->stream->soundbuffersize) ||
(request->location+request->bytestoread-request->stream->startinglocation >= request->stream->totalsize))) {
request->stream->soundbuffer->Play(0,0,DSBPLAY_LOOPING);
request->stream->fillstatus = FILL_PLAYING;
}
}
// REMOVE THIS REQUEST
s_critsect.Enter();
processed = TRUE;
HANDLE event = request->event;
if (request->autodelrequest)
s_cdreqlist.DeleteNode(request);
else
s_cdreqlist.UnlinkNode(request);
s_critsect.Leave();
if (event)
SetEvent(event);
}
} while (processed);
WaitForSingleObject(s_cdevent,s_streamlist.IsEmpty() ? INFINITE : 250);
}
s_cdshutdown = FALSE;
_endthreadex(0);
return 0;
}
//===========================================================================
static void CreateCdThread () {
if (s_cdthread != INVALID_HANDLE_VALUE)
return;
s_cdevent = CreateEvent((LPSECURITY_ATTRIBUTES)NULL,
0,
0,
NULL);
DWORD threadid;
s_cdthread = (HANDLE)_beginthreadex((LPSECURITY_ATTRIBUTES)NULL,
0,
CdThreadProc,
NULL,
0,
&threadid);
SetThreadPriority(s_cdthread,THREAD_PRIORITY_ABOVE_NORMAL);
}
//===========================================================================
static REQUESTPTR IssueRequest (FILEPTR file,
DWORD offset,
LPVOID buffer,
LPDIRECTSOUNDBUFFER soundbuffer,
DWORD soundbufferoffset,
AUDIOSTREAMPTR stream,
DWORD bytestoread,
DWORD requiredms,
DWORD dependentsequence,
HANDLE event,
BOOL autodelrequest,
BOOL triggerreadthread,
DWORD *sequence) {
VALIDATEBEGIN;
VALIDATE(buffer || soundbuffer);
VALIDATEEND;
// CREATE A REQUEST RECORD
s_critsect.Enter();
static DWORD currsequence = 0;
REQUESTPTR request = s_cdreqlist.NewNode();
DWORD requestsequence = 0;
request->event = event;
request->file = file;
request->location = offset;
request->approxarchivelocation = file->block->offset+offset/2;
request->requiredcompletiontime = GetTickCount()+requiredms;
request->buffer = buffer;
request->soundbuffer = soundbuffer;
request->soundbufferoffset = soundbufferoffset;
request->stream = stream;
request->bytestoread = bytestoread;
request->autodelrequest = autodelrequest;
request->dependentsequence = dependentsequence;
do
requestsequence = request->sequence = ++currsequence;
while (!requestsequence);
s_critsect.Leave();
// TRIGGER THE CD READ THREAD
if (triggerreadthread)
SetEvent(s_cdevent);
if (sequence)
*sequence = requestsequence;
return request;
}
/****************************************************************************
*
* SUPPORT/UTILITY FUNCTIONS
*
***/
//===========================================================================
static void BuildDefaultBasePath () {
GetModuleFileName(GetModuleHandle(NULL),s_basepath,MAX_PATH);
{
LPTSTR separator = SStrChr(s_basepath,'\\');
while (separator && SStrChr(separator+1,'\\'))
separator = SStrChr(separator+1,'\\');
if (separator)
*separator = 0;
}
SStrPack(s_basepath,"\\",MAX_PATH);
}
//===========================================================================
static BOOL CheckArchiveHandle (HSARCHIVE archive, ARCHIVEPTR *archiveptr) {
// CONVERT THE FILE HANDLE INTO AN ARCHIVE POINTER
*archiveptr = (ARCHIVEPTR)archive;
// DETERMINE WHETHER THE ARCHIVE POINTER IS VALID
BOOL valid = FALSE;
ITERATELIST(ARCHIVEREC,s_archivelist,currptr)
if (currptr == *archiveptr) {
valid = TRUE;
break;
}
// IF IT IS VALID, RETURN SUCCESS
if (valid)
return TRUE;
// OTHERWISE, SET THE ERROR CODE
SErrSetLastError(SFILE_ERROR_INVALID_HANDLE);
// GENERATE AN ERROR MESSAGE
switch (s_ioerrormode) {
case SFILE_ERRORMODE_CUSTOM:
if (s_ioerrorproc)
s_ioerrorproc("",
SFILE_ERROR_INVALID_HANDLE);
break;
case SFILE_ERRORMODE_FATAL:
SErrDisplayError(SFILE_ERROR_INVALID_HANDLE,
"HSARCHIVE",
SERR_LINECODE_HANDLE,
NULL,
TRUE,
1);
break;
}
return FALSE;
}
//===========================================================================
static BOOL CheckFileHandle (HSFILE file, FILEPTR *fileptr) {
// CONVERT THE FILE HANDLE INTO A FILE POINTER
*fileptr = (FILEPTR)file;
// DETERMINE WHETHER THE FILE POINTER IS VALID
BOOL valid = FALSE;
ITERATELIST(FILEREC,s_filelist,currptr)
if (currptr == *fileptr) {
valid = TRUE;
break;
}
// IF IT IS VALID, RETURN SUCCESS
if (valid)
return TRUE;
// OTHERWISE, SET THE ERROR CODE
SErrSetLastError(SFILE_ERROR_INVALID_HANDLE);
// GENERATE AN ERROR MESSAGE
switch (s_ioerrormode) {
case SFILE_ERRORMODE_CUSTOM:
if (s_ioerrorproc)
s_ioerrorproc("",
SFILE_ERROR_INVALID_HANDLE);
break;
case SFILE_ERRORMODE_FATAL:
SErrDisplayError(SFILE_ERROR_INVALID_HANDLE,
"HSFILE",
SERR_LINECODE_HANDLE,
NULL,
TRUE,
1);
break;
}
return FALSE;
}
//===========================================================================
static BOOL inline CheckForCdRom (LPCTSTR path) {
// DETERMINE THE ROOT PATH OF THE DRIVE
char rootpath[4];
SStrCopy(rootpath,path,4);
// GET THE DRIVE TYPE
UINT drivetype = GetDriveType(rootpath);
// GET THE FILE SYSTEM INFORMATION. RETURN FAILURE IF THERE IS NO MEDIA
// IN THE DRIVE.
DWORD fsflags = 0;
char fsname[MAX_PATH];
ZeroMemory(fsname,MAX_PATH);
if (!GetVolumeInformation(rootpath,NULL,0,NULL,NULL,&fsflags,fsname,MAX_PATH))
return 0;
// GET THE SECTOR FORMAT AND NUMBER OF FREE SECTORS
DWORD sectorspercluster = 0;
DWORD bytespersector = 0;
DWORD freeclusters = 0;
DWORD totalclusters = 0;
if (!GetDiskFreeSpace(rootpath,&sectorspercluster,&bytespersector,&freeclusters,&totalclusters))
return 0;
// CONFIRM THAT:
// 1. THE DRIVE TYPE IS CD-ROM
// 2. THE FILE SYSTEM IS CDFS OR UNKNOWN (BLANK)
// 3. THE FILE SYSTEM DOES NOT SUPPORT UNICODE
// 4. THE SECTORS ARE 2048 BYTES EACH
// 5. THERE ARE NO FREE CLUSTERS ON THE DISK
DWORD value = drivetype ^ (*(LPDWORD)fsname)
^ (fsflags & FS_UNICODE_STORED_ON_DISK)
^ bytespersector
^ freeclusters;
WORD check = LOWORD(value) ^ HIWORD(value);
return ((check == 0x1F00) || (check == 0x0805));
}
//===========================================================================
static void ConvertRelativePathName (LPCTSTR inputpath, LPTSTR outputpath) {
// DETERMINE THE BASE PATH
if (!s_basepath[0])
BuildDefaultBasePath();
// COMBINE THE BASE PATH AND THE INPUT PATH TO FORM THE OUTPUT PATH
char absolutepath[MAX_PATH] = "";
wsprintf(absolutepath,"%s%s",s_basepath,inputpath);
_fullpath(outputpath,absolutepath,MAX_PATH);
}
//===========================================================================
static BOOL FindChunk (HSFILE handle, FOURCC ckid, CKINFO *pck) {
struct {
FOURCC ckid;
DWORD size;
} ckhdr;
for (;;) {
if (!SFileReadFile(handle,&ckhdr,sizeof(ckhdr),NULL,NULL))
return 0;
if (ckhdr.ckid == ckid)
break;
if (SFileSetFilePointer(handle,ckhdr.size,NULL,FILE_CURRENT) == 0xFFFFFFFF)
return 0;
}
pck->size = ckhdr.size;
pck->offset = SFileSetFilePointer(handle,0,NULL,FILE_CURRENT);
return (pck->offset != 0xFFFFFFFF);
}
/****************************************************************************
*
* EXPORTED FUNCTIONS
*
***/
//===========================================================================
BOOL APIENTRY SFileAuthenticateArchive (HSARCHIVE handle,
DWORD *extendedresult) {
TRACEOUT(TRACEHANDLE,
"SFileAuthenticateArchive(0x%x,*extendedresult)",
handle);
if (extendedresult)
*extendedresult = SFILE_AUTH_UNABLETOAUTHENTICATE;
// CHECK THE ARCHIVE HANDLE
ARCHIVEPTR archiveptr;
if (!CheckArchiveHandle(handle,&archiveptr))
return FALSE;
// DO A PRELIMINARY CHECK FOR A SIGNATURE BLOCK IN THIS ARCHIVE BEFORE
// WE GO TO ALL THE TROUBLE OF BINDING TO THE CRYPTO API AND SECURITY
// SUPPORT PROVIDER
LCID defaultlcid = MAKELCID(MAKELANGID(LANG_NEUTRAL,SUBLANG_NEUTRAL),SORT_DEFAULT);
if (SearchHashTable(archiveptr,SIGNATUREFILE,defaultlcid) == 0xFFFFFFFF) {
if (extendedresult)
*extendedresult = SFILE_AUTH_NOSIGNATURE;
SErrSetLastError(SFILE_ERROR_NOT_AUTHENTICATED);
return 0;
}
CRYPTOAPIPTR cryptoapi = NULL;
HCRYPTHASH hash = (HCRYPTHASH)0;
HCRYPTKEY key = (HCRYPTKEY)0;
HINSTANCE lib = (HINSTANCE)0;
HANDLE map = (HANDLE)0;
HCRYPTPROV provider = (HCRYPTPROV)0;
BOOL result = 0;
LPVOID view = NULL;
TRY {
// BIND TO THE CRYPTOGRAPHY LIBRARY
lib = LoadLibrary("advapi32.dll");
if (!lib)
LEAVE;
cryptoapi = (CRYPTOAPIPTR)ALLOC(sizeof(CRYPTOAPI));
#define BIND(a,b) *(void **)&cryptoapi->##a = GetProcAddress(lib,b); \
if (!cryptoapi->##a) LEAVE;
BIND(CryptAcquireContext ,"CryptAcquireContextA" );
BIND(CryptCreateHash ,"CryptCreateHash" );
BIND(CryptDestroyHash ,"CryptDestroyHash" );
BIND(CryptDestroyKey ,"CryptDestroyKey" );
BIND(CryptHashData ,"CryptHashData" );
BIND(CryptImportKey ,"CryptImportKey" );
BIND(CryptReleaseContext ,"CryptReleaseContext" );
BIND(CryptSignHash ,"CryptSignHashA" );
BIND(CryptVerifySignature,"CryptVerifySignatureA");
#undef BIND
// INITIALIZE SECURITY SUPPORT
if (!cryptoapi->CryptAcquireContext(&provider,KEYCONTAINER,MS_DEF_PROV,PROV_RSA_FULL,0))
if (!cryptoapi->CryptAcquireContext(&provider,KEYCONTAINER,MS_DEF_PROV,PROV_RSA_FULL,CRYPT_NEWKEYSET))
LEAVE;
// MAP THE ENTIRE ARCHIVE FILE INTO MEMORY
map = CreateFileMapping(archiveptr->handle,NULL,PAGE_READONLY | SEC_COMMIT,0,0,NULL);
if (!map)
LEAVE;
view = MapViewOfFile(map,FILE_MAP_READ,0,0,0);
if (!view)
LEAVE;
// DETERMINE THE LOCATION OF THE SIGNATURE BLOCK
DWORD index = SearchHashTable(archiveptr,SIGNATUREFILE,defaultlcid);
if (index == 0xFFFFFFFF)
LEAVE;
BLOCKENTRYPTR block = archiveptr->blocktable+(archiveptr->hashtable+index)->block;
if ((block->flags & (MPQ_COMPRESSEDMASK | MPQ_ENCRYPTED)) ||
(block->sizefile <= sizeof(SIGNATUREHEADER)) ||
!block->offset) {
if (extendedresult)
*extendedresult = SFILE_AUTH_BADSIGNATURE;
LEAVE;
}
// DETERMINE THE COMPANY ID
DWORD companyid = ((SIGNATUREHEADERPTR)((LPBYTE)view+block->offset))->companyid;
if (companyid >= AUTHCOMPANIES) {
if (extendedresult)
*extendedresult = SFILE_AUTH_UNKNOWNSIGNATURE;
LEAVE;
}
// LOAD THE APPROPRIATE COMPANY'S PUBLIC KEY INTO THE KEY DATABASE
{
HRSRC resource = FindResource(StormGetInstance(),s_authcompany[companyid].keyname,"#256");
HGLOBAL handle = LoadResource(StormGetInstance(),resource);
LPVOID ptr = LockResource(handle);
if (ptr)
cryptoapi->CryptImportKey(provider,
(LPBYTE)ptr,
SizeofResource(StormGetInstance(),resource),
NULL,
0,
&key);
FreeResource(handle);
}
if (!key)
LEAVE;
// HASH THE ARCHIVE FILE, REPLACING THE SIGNATURE BLOCK WITH ZEROES.
// IF THIS ARCHIVE FILE IS EMBEDDED IN A LARGER FILE, ONLY HASH THE
// MOPAQ PORTION.
if (!cryptoapi->CryptCreateHash(provider,CALG_MD5,NULL,0,&hash))
LEAVE;
if (!cryptoapi->CryptHashData(hash,
(LPBYTE)view+archiveptr->startinglocation,
block->offset-archiveptr->startinglocation,
0))
LEAVE;
{
LPVOID zeroblock = ALLOCZERO(block->sizefile);
BOOL hashresult = cryptoapi->CryptHashData(hash,
(LPBYTE)zeroblock,
block->sizefile,
0);
FREE(zeroblock);
if (!hashresult)
LEAVE;
}
if (block->offset+block->sizefile < archiveptr->startinglocation+archiveptr->fileheader->filesize)
if (!cryptoapi->CryptHashData(hash,
(LPBYTE)view+block->offset+block->sizefile,
archiveptr->startinglocation+archiveptr->fileheader->filesize
-(block->offset+block->sizefile),
0))
LEAVE;
// VERIFY THE SIGNATURE BLOCK AGAINST THE HASH VALUE
if (cryptoapi->CryptVerifySignature(hash,
(LPBYTE)view+block->offset+sizeof(SIGNATUREHEADER),
block->sizefile-sizeof(SIGNATUREHEADER),
key,
NULL,
0)) {
result = 1;
if (extendedresult)
*extendedresult = s_authcompany[companyid].authresult;
}
else if (extendedresult)
*extendedresult = SFILE_AUTH_BADSIGNATURE;
}
FINALLY {
if (hash)
cryptoapi->CryptDestroyHash(hash);
if (key)
cryptoapi->CryptDestroyKey(key);
if (view)
UnmapViewOfFile(view);
if (map)
CloseHandle(map);
if (provider) {
cryptoapi->CryptReleaseContext(provider,0);
cryptoapi->CryptAcquireContext(&provider,KEYCONTAINER,MS_DEF_PROV,PROV_RSA_FULL,CRYPT_DELETEKEYSET);
}
if (cryptoapi)
FREE(cryptoapi);
if (lib)
FreeLibrary(lib);
}
if (!result)
SErrSetLastError(SFILE_ERROR_NOT_AUTHENTICATED);
return result;
}
//===========================================================================
BOOL APIENTRY SFileCloseArchive (HSARCHIVE handle) {
TRACEOUT(TRACEHANDLE,
"SFileCloseArchive(0x%x)",
handle);
// CHECK THE ARCHIVE HANDLE
ARCHIVEPTR archiveptr;
if (!CheckArchiveHandle(handle,&archiveptr))
return FALSE;
// CLOSE ALL FILES ASSOCIATED WITH THIS ARCHIVE
{
FILEPTR curr = s_filelist.Head();
while (curr) {
FILEPTR next = curr->Next();
if (curr->archive == archiveptr)
SFileCloseFile((HSFILE)curr);
curr = next;
}
}
// FREE MEMORY
if (archiveptr->sectorbuffer)
FREE(archiveptr->sectorbuffer);
if (archiveptr->blocktable)
FREE(archiveptr->blocktable);
if (archiveptr->fileheader)
DEL(archiveptr->fileheader);
if (archiveptr->hashtable)
FREE(archiveptr->hashtable);
// CLOSE THE ARCHIVE FILE
CloseHandle(archiveptr->handle);
// UNLINK AND FREE THE ARCHIVE
s_critsect.Enter();
s_archivelist.DeleteNode(archiveptr);
s_critsect.Leave();
return 1;
}
//===========================================================================
BOOL APIENTRY SFileCloseFile (HSFILE handle) {
TRACEOUT(TRACEHANDLE,
"SFileCloseFile(0x%x)",
handle);
// CHECK THE FILE HANDLE
FILEPTR fileptr;
if (!CheckFileHandle(handle,&fileptr))
return FALSE;
// END ANY DDA STREAMS ASSOCIATED WITH THIS FILE
if (fileptr->dda)
SFileDdaEnd(handle);
// WAIT UNTIL ALL OUTSTANDING READ OPERATIONS FOR THIS FILE COMPLETE.
// IF WE ARE IN DEBUG MODE, DISPLAY A WARNING IF THE APPLICATION TRIES
// TO CLOSE A FILE WHILE THERE ARE OUTSTANDING OVERLAPPED READ OPERATIONS
// ON THAT FILE.
s_critsect.Enter();
for (;;) {
BOOL outstanding = FALSE;
ITERATELIST(REQUEST,s_cdreqlist,curr)
if (curr->file == fileptr) {
outstanding = TRUE;
break;
}
if (outstanding) {
#ifdef _DEBUG
SErrDisplayError(ERROR_IO_PENDING,
"SFileCloseFile()",
SERR_LINECODE_FUNCTION,
NULL,
TRUE);
#endif
Sleep(1);
}
else
break;
}
// CLOSE THE FILE HANDLE IF USED
if (fileptr->handle != INVALID_HANDLE_VALUE)
CloseHandle(fileptr->handle);
// CLEAR THE SECTOR CACHE
if (fileptr->archive)
fileptr->archive->sectorfile = NULL;
// FREE THE SECTOR OFFSET TABLE
if (fileptr->sectoroffsettable)
FREE(fileptr->sectoroffsettable);
// FREE THE READ AHEAD BUFFER
if (fileptr->readaheadbuffer)
FREE(fileptr->readaheadbuffer);
// UNLINK AND FREE THE FILE
s_filelist.DeleteNode(fileptr);
s_critsect.Leave();
return 1;
}
//===========================================================================
BOOL APIENTRY SFileDdaBegin (HSFILE handle,
DWORD buffersize,
DWORD flags) {
TRACEOUT(TRACEHANDLE,
"SFileDdaBegin(0x%x,%u,0x%x)",
handle,buffersize,flags);
return SFileDdaBeginEx(handle,
buffersize,
flags,
0,
0x7FFFFFFF,
0x7FFFFFFF,
NULL);
}
//===========================================================================
BOOL APIENTRY SFileDdaBeginEx (HSFILE handle,
DWORD buffersize,
DWORD flags,
DWORD offset,
LONG volume,
LONG pan,
LPVOID reserved) {
VALIDATEBEGIN;
VALIDATE(buffersize);
VALIDATEEND;
TRACEOUT(TRACEHANDLE,
"SFileDdaBeginEx(0x%x,%u,0x%x,%u,%d,%d,%u)",
handle,buffersize,flags,offset,volume,pan,reserved);
if (!s_directsound) {
SErrSetLastError(SFILE_ERROR_NOT_INITIALIZED);
return 0;
}
// CHECK THE FILE HANDLE
FILEPTR fileptr;
if (!CheckFileHandle(handle,&fileptr))
return FALSE;
// STOP PLAYING THIS FILE IF IT IS ALREADY PLAYING
if (fileptr->dda)
SFileDdaEnd(handle);
// ENSURE THAT THE FILE IS IN AN ARCHIVE
if ((fileptr->handle != INVALID_HANDLE_VALUE) || !fileptr->archive) {
SErrSetLastError(SFILE_ERROR_NOT_IN_ARCHIVE);
return 0;
}
// MARK THIS FILE AS PLAYING
fileptr->dda = 1;
// CREATE A SOUND READ BUFFER IF NECESSARY
if (!s_soundreadbuffer)
s_soundreadbuffer = ALLOC(WAVECHUNKSIZE);
// IF THE FILE'S ARCHIVE IS NOT ON CD, START PROCESSING IT AS A CD ARCHIVE
// ANYWAY, BECAUSE THE CD READ THREAD DOES THE WORK OF READING BACKGROUND
// STREAM DATA
if (fileptr->archive->cdrom == CDROM_FALSE) {
fileptr->archive->cdrom = CDROM_USEDFORDDA;
if (s_cdthread == INVALID_HANDLE_VALUE)
CreateCdThread();
}
// ADJUST THE BUFFER SIZE SO IT IS A MULTIPLE OF 64K AND AT LEAST 128K
if (buffersize & (WAVECHUNKSIZE-1))
buffersize += WAVECHUNKSIZE-(buffersize & (WAVECHUNKSIZE-1));
if (buffersize < 2*WAVECHUNKSIZE)
buffersize = 2*WAVECHUNKSIZE;
// READ THE WAVE FILE HEADER
WAVEFORMATEX format;
DWORD startinglocation;
DWORD totalsize;
{
SFileSetFilePointer(handle,0,NULL,FILE_BEGIN);
MMCKINFO mmck;
if (!SFileReadFile(handle,&mmck,sizeof(FOURCC)*2+sizeof(DWORD),NULL,NULL))
return 0;
if ((mmck.ckid != FOURCC_RIFF) || (mmck.fccType != mmioFOURCC('W','A','V','E'))) {
SErrSetLastError(SFILE_ERROR_INVALID_DATA);
return 0;
}
CKINFO info;
if (!FindChunk(handle,mmioFOURCC('f','m','t',' '),&info)) {
SErrSetLastError(SFILE_ERROR_INVALID_DATA);
return 0;
}
if (info.size < sizeof(PCMWAVEFORMAT)) {
SErrSetLastError(SFILE_ERROR_INVALID_DATA);
return 0;
}
PCMWAVEFORMAT pcm;
if (!SFileReadFile(handle,&pcm,sizeof(PCMWAVEFORMAT),NULL,NULL))
return 0;
if (SFileSetFilePointer(handle,info.size-sizeof(PCMWAVEFORMAT),NULL,FILE_CURRENT) == 0xFFFFFFFF)
return 0;
format.wFormatTag = pcm.wf.wFormatTag;
format.nChannels = pcm.wf.nChannels;
format.nSamplesPerSec = pcm.wf.nSamplesPerSec;
format.nAvgBytesPerSec = pcm.wf.nAvgBytesPerSec;
format.nBlockAlign = pcm.wf.nBlockAlign;
format.wBitsPerSample = pcm.wBitsPerSample;
format.cbSize = 0;
if (!FindChunk(handle,mmioFOURCC('d','a','t','a'),&info)) {
SErrSetLastError(SFILE_ERROR_INVALID_DATA);
return 0;
}
startinglocation = info.offset;
totalsize = info.size;
}
// CREATE A STREAM RECORD
s_critsect.Enter();
AUDIOSTREAMPTR stream = s_streamlist.NewNode();
stream->file = fileptr;
stream->soundbuffersize = buffersize;
stream->bytespersecond = format.nAvgBytesPerSec;
stream->loop = ((flags & SFILE_DDA_LOOP) != 0);
stream->fillstatus = FILL_UNREQUESTED;
stream->startinglocation = startinglocation+min(offset,totalsize-startinglocation);
stream->totalsize = totalsize;
stream->volume = volume;
stream->pan = pan;
stream->fillvalue = (format.wBitsPerSample == 8) ? 0x80 : 0x00;
// CREATE A SECONDARY SOUND BUFFER
{
DSBUFFERDESC desc;
ZeroMemory(&desc,sizeof(DSBUFFERDESC));
desc.dwSize = sizeof(DSBUFFERDESC);
desc.dwFlags = (DSBCAPS_CTRLPAN | DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLFREQUENCY);
desc.dwBufferBytes = buffersize;
desc.lpwfxFormat = &format;
s_directsound->CreateSoundBuffer(&desc,&stream->soundbuffer,NULL);
if (stream->soundbuffer) {
if (stream->volume != 0x7FFFFFFF)
stream->soundbuffer->SetVolume(stream->volume);
if (stream->pan != 0x7FFFFFFF)
stream->soundbuffer->SetPan(stream->pan);
}
}
s_critsect.Leave();
// TRIGGER THE CD READ THREAD
SetEvent(s_cdevent);
return 1;
}
//===========================================================================
BOOL APIENTRY SFileDdaDestroy () {
TRACEOUT(TRACEHANDLE,
"SFileDdaDestroy()");
// FREE ALL STREAMS
s_critsect.Enter();
{
AUDIOSTREAMPTR curr;
while ((curr = s_streamlist.Head()) != NULL) {
curr->file->dda = TRUE;
s_critsect.Leave();
SFileDdaEnd((HSFILE)curr->file);
s_critsect.Enter();
}
}
s_critsect.Leave();
// DELETE THE POINTER TO THE DIRECTSOUND OBJECT
s_directsound = NULL;
// FREE THE SOUND READ BUFFER
if (s_soundreadbuffer) {
FREE(s_soundreadbuffer);
s_soundreadbuffer = NULL;
}
return 1;
}
//===========================================================================
BOOL APIENTRY SFileDdaEnd (HSFILE handle) {
TRACEOUT(TRACEHANDLE,
"SFileDdaEnd(0x%x)",
handle);
// CHECK THE FILE HANDLE
FILEPTR fileptr;
if (!CheckFileHandle(handle,&fileptr))
return FALSE;
// FREE ALL STREAMS ASSOCIATED WITH THIS FILE
BOOL again;
do {
again = FALSE;
s_critsect.Enter();
AUDIOSTREAMPTR curr = s_streamlist.Head();
while (curr) {
AUDIOSTREAMPTR next = curr->Next();
if (curr->file == fileptr) {
curr->fillstatus = FILL_CLOSING;
// CHECK WHETHER THERE ARE OUTSTANDING READ REQUESTS FOR THE STREAM
BOOL found = FALSE;
REQUESTPTR currreq = s_cdreqlist.Head();
while (currreq && !found) {
if ((currreq->stream == curr) ||
(currreq->soundbuffer == curr->soundbuffer))
found = TRUE;
currreq = currreq->Next();
}
// IF NOT, DELETE THE STREAM
if (found)
again = TRUE;
else {
if (curr->soundbuffer)
curr->soundbuffer->Release();
next = s_streamlist.DeleteNode(curr);
}
}
curr = next;
}
s_critsect.Leave();
if (again)
Sleep(1);
} while (again);
fileptr->dda = FALSE;
return TRUE;
}
//===========================================================================
BOOL APIENTRY SFileDdaGetPos (HSFILE handle,
DWORD *position,
DWORD *maxposition) {
TRACEOUT(TRACEHANDLE,
"SFileDdaGetPos(0x%x,*position,*maxposition)",
handle);
if (position)
*position = 0;
if (maxposition)
*maxposition = 0;
// CHECK THE FILE HANDLE
FILEPTR fileptr;
if (!CheckFileHandle(handle,&fileptr))
return FALSE;
// ENTER THE CRITICAL SECTION
s_critsect.Enter();
// FIND THE STREAM ASSOCIATED WITH THIS FILE
AUDIOSTREAMPTR curr = s_streamlist.Head();
while (curr && (curr->file != fileptr))
curr = curr->Next();
if (!curr) {
s_critsect.Leave();
SErrSetLastError(SFILE_ERROR_NOT_PLAYING);
return 0;
}
// DETERMINE THE CURRENT PLAY POSITION
DWORD playposition = 0;
if ((curr->fillstatus == FILL_PLAYING) ||
(curr->fillstatus == FILL_CLOSING)) {
DWORD playcursor = 0;
DWORD writecursor = 0;
curr->soundbuffer->GetCurrentPosition(&playcursor,&writecursor);
DWORD writeposition = curr->file->location+curr->bytespastend-curr->startinglocation;
playposition = (writeposition/curr->soundbuffersize)*curr->soundbuffersize+playcursor;
if (playposition >= writeposition)
if (playposition > curr->soundbuffersize)
playposition -= curr->soundbuffersize;
else
playposition = 0;
playposition = min(playposition,curr->totalsize-curr->startinglocation);
}
// RETURN THE CURRENT POSITIONS
if (position)
*position = playposition;
if (maxposition)
*maxposition = curr->totalsize-curr->startinglocation;
// LEAVE THE CRITICAL SECTION
s_critsect.Leave();
return 1;
}
//===========================================================================
BOOL APIENTRY SFileDdaGetVolume (HSFILE handle,
LONG *volume,
LONG *pan) {
TRACEOUT(TRACEHANDLE,
"SFileDdaGetVolume(0x%x,*volume,*pan)",
handle);
if (volume)
*volume = 0;
if (pan)
*pan = 0;
// CHECK THE FILE HANDLE
FILEPTR fileptr;
if (!CheckFileHandle(handle,&fileptr))
return FALSE;
// ENTER THE CRITICAL SECTION
s_critsect.Enter();
// FIND THE STREAM ASSOCIATED WITH THIS FILE
AUDIOSTREAMPTR curr = s_streamlist.Head();
while (curr && (curr->file != fileptr))
curr = curr->Next();
if (!curr) {
s_critsect.Leave();
SErrSetLastError(SFILE_ERROR_NOT_PLAYING);
return 0;
}
// IF WE DON'T KNOW WHAT THE VOLUME OR PAN OF THIS AUDIO STREAM IS,
// DETERMINE IT NOW
if (curr->volume == 0x7FFFFFFF)
curr->soundbuffer->GetVolume(&curr->volume);
if (curr->pan == 0x7FFFFFFF)
curr->soundbuffer->GetPan(&curr->pan);
// RETURN THE REQUESTED VALUES
if (volume)
*volume = curr->volume;
if (pan)
*pan = curr->pan;
// LEAVE THE CRITICAL SECTION
s_critsect.Leave();
return 1;
}
//===========================================================================
BOOL APIENTRY SFileDdaInitialize (LPDIRECTSOUND directsound) {
VALIDATEBEGIN;
VALIDATE(directsound);
VALIDATEEND;
TRACEOUT(TRACEHANDLE,
"SFileDdaInitialize(0x%x)",
directsound);
s_directsound = directsound;
return 1;
}
//===========================================================================
BOOL APIENTRY SFileDdaSetVolume (HSFILE handle,
LONG volume,
LONG pan) {
TRACEOUT(TRACEHANDLE,
"SFileDdaSetVolume(0x%x,%d,%d)",
handle,volume,pan);
// CHECK THE FILE HANDLE
FILEPTR fileptr;
if (!CheckFileHandle(handle,&fileptr))
return FALSE;
// ENTER THE CRITICAL SECTION
s_critsect.Enter();
// FIND THE STREAM ASSOCIATED WITH THIS FILE
AUDIOSTREAMPTR curr = s_streamlist.Head();
while (curr && (curr->file != fileptr))
curr = curr->Next();
if (!curr) {
s_critsect.Leave();
SErrSetLastError(SFILE_ERROR_NOT_PLAYING);
return 0;
}
// ADJUST THE SETTINGS
if ((volume != 0x7FFFFFFF) && (volume != curr->volume)) {
curr->volume = volume;
curr->soundbuffer->SetVolume(volume);
}
if ((pan != 0x7FFFFFFF) && (pan != curr->pan)) {
curr->pan = pan;
curr->soundbuffer->SetPan(pan);
}
// LEAVE THE CRITICAL SECTION
s_critsect.Leave();
return 1;
}
//===========================================================================
BOOL APIENTRY SFileDestroy () {
TRACEOUT(TRACEHANDLE,
"SFileDestroy()");
// WAIT FOR ALL OUTSTANDING REQUESTS TO COMPLETE
while (!s_cdreqlist.IsEmpty())
Sleep(10);
// SHUT DOWN THE CD THREAD READ ROUTINE
if (s_cdthread != INVALID_HANDLE_VALUE) {
s_cdshutdown = 1;
SetEvent(s_cdevent);
WaitForSingleObject(s_cdthread,INFINITE);
if (s_cdevent != INVALID_HANDLE_VALUE) {
CloseHandle(s_cdevent);
s_cdevent = INVALID_HANDLE_VALUE;
}
if (s_cdthread != INVALID_HANDLE_VALUE) {
CloseHandle(s_cdthread);
s_cdthread = INVALID_HANDLE_VALUE;
}
}
// DESTROY DDA
SFileDdaDestroy();
// DELETE THE OPEN FILES AND ARCHIVES
{
FILEPTR curr;
while ((curr = s_filelist.Head()) != NULL) {
REPORTRESOURCELEAK(HSFILE);
SFileCloseFile((HSFILE)curr);
}
}
{
ARCHIVEPTR curr;
while ((curr = s_archivelist.Head()) != NULL) {
REPORTRESOURCELEAK(HSARCHIVE);
SFileCloseArchive((HSARCHIVE)curr);
}
}
// DELETE THE HASH TABLES
if (s_hashsource) {
FREE(s_hashsource);
s_hashsource = NULL;
}
// DELETE THE DECOMPRESSION DATA
if (s_explodebuffer) {
FREE(s_explodebuffer);
s_explodebuffer = NULL;
}
return 1;
}
//===========================================================================
BOOL APIENTRY SFileEnableDirectAccess (BOOL enable) {
TRACEOUT(TRACEHANDLE,
"SFileEnableDirectAccess(%u)",
enable);
s_enabledirect = enable;
return 1;
}
//===========================================================================
BOOL APIENTRY SFileGetArchiveInfo (HSARCHIVE archive,
int *priority,
BOOL *cdrom) {
if (priority)
*priority = 0;
if (cdrom)
*cdrom = FALSE;
// CHECK THE ARCHIVE HANDLE
ARCHIVEPTR archiveptr;
if (!CheckArchiveHandle(archive,&archiveptr))
return FALSE;
// RETURN THE INFORMATION
if (priority)
*priority = archiveptr->priority;
if (cdrom)
*cdrom = (archiveptr->cdrom == CDROM_TRUE);
return TRUE;
}
//===========================================================================
BOOL APIENTRY SFileGetArchiveName (HSARCHIVE archive,
LPTSTR buffer,
DWORD bufferchars) {
VALIDATEBEGIN;
VALIDATEANDBLANK(buffer);
VALIDATEEND;
TRACEOUT(TRACEHANDLE,
"SFileGetArchiveName(0x%x,0x%x,%u)",
archive,buffer,bufferchars);
// CHECK THE ARCHIVE HANDLE
ARCHIVEPTR archiveptr;
if (!CheckArchiveHandle(archive,&archiveptr))
return FALSE;
SStrCopy(buffer,archiveptr->name,bufferchars);
return 1;
}
//===========================================================================
BOOL APIENTRY SFileGetBasePath (LPTSTR buffer,
DWORD bufferchars) {
TRACEOUT(TRACEHANDLE,
"SFileGetBasePath(0x%x,%u)",
buffer,bufferchars);
if (!s_basepath[0])
BuildDefaultBasePath();
SStrCopy(buffer,s_basepath,bufferchars);
return TRUE;
}
//===========================================================================
BOOL APIENTRY SFileGetFileArchive (HSFILE file,
HSARCHIVE *archive) {
VALIDATEBEGIN;
VALIDATEANDBLANK(archive);
VALIDATEEND;
TRACEOUT(TRACEHANDLE,
"SFileGetFileArchive(0x%x,*archive)",
file);
// CHECK THE FILE HANDLE
FILEPTR fileptr;
if (!CheckFileHandle(file,&fileptr))
return FALSE;
*archive = (HSARCHIVE)(fileptr->archive);
return 1;
}
//===========================================================================
BOOL APIENTRY SFileGetFileName (HSFILE file,
LPTSTR buffer,
DWORD bufferchars) {
VALIDATEBEGIN;
VALIDATEANDBLANK(buffer);
VALIDATEEND;
TRACEOUT(TRACEHANDLE,
"SFileGetFileName(0x%x,0x%x,%u)",
file,buffer,bufferchars);
// CHECK THE FILE HANDLE
FILEPTR fileptr;
if (!CheckFileHandle(file,&fileptr))
return FALSE;
SStrCopy(buffer,fileptr->name,bufferchars);
return 1;
}
//===========================================================================
DWORD APIENTRY SFileGetFileSize (HSFILE handle,
LPDWORD filesizehigh) {
TRACEOUT(TRACEHANDLE,
"SFileGetFileSize(0x%x,*filesizehigh)",
handle);
if (filesizehigh)
*filesizehigh = NULL;
// CHECK THE FILE HANDLE
FILEPTR fileptr;
if (!CheckFileHandle(handle,&fileptr))
return 0xFFFFFFFF;
// IF THIS HANDLE REPRESENTS A REAL FILE, FORWARD THE REQUEST TO THE
// STANDARD WIN32 API
if (fileptr->handle != INVALID_HANDLE_VALUE)
return GetFileSize(fileptr->handle,filesizehigh);
// RETURN THE FILE SIZE
return fileptr->block->sizefile;
}
//===========================================================================
BOOL APIENTRY SFileOpenArchive (LPCTSTR archivename,
int priority,
BOOL cdonly,
HSARCHIVE *handle) {
VALIDATEBEGIN;
VALIDATEANDBLANK(handle);
VALIDATE(archivename);
VALIDATE(*archivename);
VALIDATEEND;
TRACEOUT(TRACEHANDLE,
"SFileOpenArchive(\"%s\",%d,%u,*handle)",
archivename,priority,cdonly);
// INITIALIZE THE HASH TABLES IF NECESSARY
if (!s_hashsource) {
s_hashsource = (LPDWORD)ALLOC(5*256*sizeof(DWORD));
InitializeHashSource();
}
// IF THE ARCHIVE FILENAME DOES NOT CONTAIN AN ABSOLUTE PATH AND THE
// ARCHIVE DOES NOT EXIST IN THE GIVEN LOCATION RELATIVE TO THE CURRENT
// DIRECTORY, CHECK FOR IT IN THE GIVEN LOCATION RELATIVE TO THE
// EXECUTABLE DIRECTORY
char localarchivename[MAX_PATH];
SStrCopy(localarchivename,archivename,MAX_PATH);
localarchivename[MAX_PATH-1] = 0;
if (GetFileAttributes(localarchivename) & FILE_ATTRIBUTE_DIRECTORY)
if ((*archivename == '\\') ||
strstr(archivename,":\\") ||
strstr(archivename,"\\\\"))
SStrCopy(localarchivename,archivename,MAX_PATH);
else
ConvertRelativePathName(archivename,localarchivename);
// DETERMINE WHETHER THIS ARCHIVE IS ON A CD-ROM DRIVE
BOOL cdrom = CheckForCdRom(localarchivename);
if (cdonly && !cdrom) {
SErrSetLastError(SFILE_ERROR_INVALID_DRIVE);
return 0;
}
// OPEN THE ARCHIVE FILE
HANDLE archivehandle = CreateFile(localarchivename,
GENERIC_READ,
FILE_SHARE_READ,
(LPSECURITY_ATTRIBUTES)NULL,
OPEN_EXISTING,
0,
NULL);
if (archivehandle == INVALID_HANDLE_VALUE)
return 0;
// CREATE A RECORD FOR THIS ARCHIVE
ARCHIVEPTR archiveptr = s_archivelist.NewNode(LIST_UNLINKED);
SStrCopy(archiveptr->name,localarchivename,MAX_PATH);
archiveptr->handle = archivehandle;
archiveptr->cdrom = cdrom ? CDROM_TRUE : CDROM_FALSE;
archiveptr->priority = priority;
// SINCE THE MOPAQ FILE MAY BE TACKED ON BELOW OTHER FILE DATA, SEARCH
// FOR THE SIGNATURE TO DETERMINE THE STARTING LOCATION OF THE MOPAQ
// FILE
archiveptr->fileheader = NEW(FILEHEADER);
DWORD bytesread;
archiveptr->startinglocation = 0;
do {
SetFilePointer(archivehandle,archiveptr->startinglocation,NULL,FILE_BEGIN);
bytesread = 0;
ReadFile(archivehandle,
archiveptr->fileheader,
sizeof(FILEHEADER),
&bytesread,
NULL);
if (bytesread != sizeof(FILEHEADER)) {
DEL(archiveptr->fileheader);
s_archivelist.DeleteNode(archiveptr);
CloseHandle(archivehandle);
SErrSetLastError(SFILE_ERROR_NOT_ARCHIVE);
return 0;
}
if (archiveptr->fileheader->signature != SIGNATURE)
archiveptr->startinglocation += 512;
} while (archiveptr->fileheader->signature != SIGNATURE);
// DETERMINE THE SECTOR SIZE AND ALLOCATE A SECTOR BUFFER
archiveptr->sectorsize = (512 << archiveptr->fileheader->sectorsizeid);
archiveptr->sectorbuffer = (LPBYTE)ALLOC(archiveptr->sectorsize);
// READ THE HASH TABLE
archiveptr->hashtable = (HASHENTRYPTR)ALLOC(archiveptr->fileheader->hashcount*sizeof(HASHENTRY));
SetFilePointer(archivehandle,archiveptr->startinglocation+archiveptr->fileheader->hashoffset,NULL,FILE_BEGIN);
ReadFileChecked(archivehandle,
archiveptr->hashtable,
archiveptr->fileheader->hashcount*sizeof(HASHENTRY),
&bytesread,
NULL,
archiveptr->name);
Decrypt((LPDWORD)archiveptr->hashtable,
archiveptr->fileheader->hashcount*sizeof(HASHENTRY),
Hash("(hash table)",HASH_ENCRYPTKEY));
// READ THE BLOCK TABLE
archiveptr->blocktable = (BLOCKENTRYPTR)ALLOC(archiveptr->fileheader->blockcount*sizeof(BLOCKENTRY));
SetFilePointer(archivehandle,archiveptr->startinglocation+archiveptr->fileheader->blockoffset,NULL,FILE_BEGIN);
ReadFileChecked(archivehandle,
archiveptr->blocktable,
archiveptr->fileheader->blockcount*sizeof(BLOCKENTRY),
&bytesread,
NULL,
archiveptr->name);
Decrypt((LPDWORD)archiveptr->blocktable,
archiveptr->fileheader->blockcount*sizeof(BLOCKENTRY),
Hash("(block table)",HASH_ENCRYPTKEY));
// IF THE STARTING LOCATION IS NOT ZERO, OFFSET ALL OF THE BLOCKS IN THE
// BLOCK TABLE
if (archiveptr->startinglocation) {
for (DWORD block = 0; block < archiveptr->fileheader->blockcount; ++block)
if ((archiveptr->blocktable+block)->offset)
(archiveptr->blocktable+block)->offset += archiveptr->startinglocation;
}
// ADD THE ARCHIVE TO OUR LINKED LIST IN PRIORITY ORDER
s_critsect.Enter();
{
ARCHIVEPTR curr = s_archivelist.Head();
while (curr && (curr->priority > priority))
curr = curr->Next();
s_archivelist.LinkNode(archiveptr,LIST_LINK_BEFORE,curr);
}
s_critsect.Leave();
// IF THIS ARCHIVE IS ON A CD-ROM AND THERE IS NO CD-ROM READ THREAD YET,
// CREATE ONE
if (cdrom && (s_cdthread == INVALID_HANDLE_VALUE))
CreateCdThread();
else if (cdonly && !cdrom) {
SFileCloseArchive((HSARCHIVE)archiveptr);
SErrSetLastError(SFILE_ERROR_INVALID_DRIVE);
return 0;
}
// RETURN A HANDLE TO THE ARCHIVE
*handle = (HSARCHIVE)archiveptr;
return 1;
}
//===========================================================================
BOOL APIENTRY SFileOpenFile (LPCTSTR filename,
HSFILE *handle) {
VALIDATEBEGIN;
VALIDATEANDBLANK(handle);
VALIDATE(filename);
VALIDATE(*filename);
VALIDATEEND;
TRACEOUT(TRACEHANDLE,
"SFileOpenFile(\"%s\",*handle)",
filename);
// IF THIS FILE IS AVAILABLE OUTSIDE OF AN ARCHIVE, OPEN IT DIRECTLY
if (s_enabledirect || s_archivelist.IsEmpty()) {
char localfilename[MAX_PATH] = "";
if ((*filename == '\\') ||
strstr(filename,":\\") ||
strstr(filename,"\\\\"))
SStrCopy(localfilename,filename,MAX_PATH);
else
ConvertRelativePathName(filename,localfilename);
HANDLE filehandle = CreateFile(localfilename,
GENERIC_READ,
FILE_SHARE_READ,
(LPSECURITY_ATTRIBUTES)NULL,
OPEN_EXISTING,
FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
if (filehandle != INVALID_HANDLE_VALUE) {
s_critsect.Enter();
FILEPTR fileptr = s_filelist.NewNode();
fileptr->handle = filehandle;
s_critsect.Leave();
*handle = (HSFILE)fileptr;
return TRUE;
}
}
// OTHERWISE, SEARCH FOR THE REQUESTED FILE IN THE CURRENTLY OPEN ARCHIVES
if (s_archivelist.IsEmpty() || !s_hashsource) {
SErrSetLastError(SFILE_ERROR_FILE_NOT_FOUND);
return 0;
}
ARCHIVEPTR archiveptr = s_archivelist.Head();
DWORD index;
do {
index = SearchHashTable(archiveptr,filename,s_lcid);
if (index == 0xFFFFFFFF)
archiveptr = archiveptr->Next();
} while (archiveptr && (index == 0xFFFFFFFF));
if (!archiveptr) {
SErrSetLastError(SFILE_ERROR_FILE_NOT_FOUND);
return 0;
}
return SFileOpenFileEx((HSARCHIVE)archiveptr,filename,0,handle);
}
//===========================================================================
BOOL APIENTRY SFileOpenFileEx (HSARCHIVE archivehandle,
LPCTSTR filename,
DWORD flags,
HSFILE *handle) {
VALIDATEBEGIN;
VALIDATEANDBLANK(handle);
VALIDATE(filename);
VALIDATE(*filename);
VALIDATEEND;
TRACEOUT(TRACEHANDLE,
"SFileOpenFileEx(0x%x,\"%s\",0x%x,*handle)",
archivehandle,filename,flags);
// CHECK THE ARCHIVE HANDLE
ARCHIVEPTR archiveptr;
if (!CheckArchiveHandle(archivehandle,&archiveptr))
return FALSE;
// DETERMINE THE HASH INDEX
DWORD index = SearchHashTable(archiveptr,filename,s_lcid);
if (index == 0xFFFFFFFF) {
SErrSetLastError(SFILE_ERROR_FILE_NOT_FOUND);
return 0;
}
// DETERMINE THE BLOCK POINTER
BLOCKENTRYPTR block = archiveptr->blocktable+(archiveptr->hashtable+index)->block;
// CONFIRM THAT THIS IS A VALID FILE
if ((!block->sizefile) ||
!(block->flags & MPQ_ALLOCATED)) {
SErrSetLastError(SFILE_ERROR_FILE_INVALID);
return 0;
}
// DETERMINE THE FILE NAME PORTION OF THE REQUESTED FILE
LPCTSTR name = filename;
while (SStrChr(name,':'))
name = SStrChr(name,':')+1;
while (SStrChr(name,'\\'))
name = SStrChr(name,'\\')+1;
// CREATE A DECRYPTION KEY BASED ON THE FILE NAME
DWORD key = Hash(name,HASH_ENCRYPTKEY);
if (block->flags & MPQ_ENCRYPTED_FIXLOC)
key = (key + (block->offset-archiveptr->startinglocation)) ^ block->sizefile;
// CREATE A RECORD FOR THE FILE
DWORD sectors = (block->sizefile+archiveptr->sectorsize-1)/archiveptr->sectorsize;
LPDWORD sectoroffsettable = NULL;
if (block->flags & MPQ_COMPRESSEDMASK)
sectoroffsettable = (LPDWORD)ALLOC((sectors+1)*sizeof(DWORD));
s_critsect.Enter();
FILEPTR fileptr = s_filelist.NewNode();
SStrCopy(fileptr->name,filename,MAX_PATH);
fileptr->handle = INVALID_HANDLE_VALUE;
fileptr->archive = archiveptr;
fileptr->block = block;
fileptr->key = key;
fileptr->sectors = sectors;
fileptr->sectoroffsettable = sectoroffsettable;
fileptr->readaheadbuffer = ALLOC(READAHEAD);
s_critsect.Leave();
// RETURN A HANDLE TO THE FILE
*handle = (HSFILE)fileptr;
return 1;
}
//===========================================================================
BOOL APIENTRY SFileOpenFileWin32 (LPCTSTR filename,
HANDLE *handle) {
VALIDATEBEGIN;
VALIDATEANDBLANK(handle);
VALIDATE(filename);
VALIDATE(*filename);
VALIDATEEND;
TRACEOUT(TRACEHANDLE,
"SFileOpenFileWin32(\"%s\",*handle)",
filename);
// IF THIS FILE IS AVAILABLE OUTSIDE OF AN ARCHIVE, OPEN IT DIRECTLY
{
char localfilename[MAX_PATH] = "";
if ((*filename == '\\') ||
strstr(filename,":\\") ||
strstr(filename,"\\\\"))
SStrCopy(localfilename,filename,MAX_PATH);
else
ConvertRelativePathName(filename,localfilename);
*handle = CreateFile(localfilename,
GENERIC_READ,
FILE_SHARE_READ,
(LPSECURITY_ATTRIBUTES)NULL,
OPEN_EXISTING,
FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
if (*handle != INVALID_HANDLE_VALUE)
return 1;
}
// OTHERWISE, SEARCH FOR THE REQUESTED FILE IN THE CURRENTLY OPEN ARCHIVES
if (s_archivelist.IsEmpty() || !s_hashsource) {
SErrSetLastError(SFILE_ERROR_FILE_NOT_FOUND);
return 0;
}
ARCHIVEPTR archiveptr = s_archivelist.Head();
DWORD index;
do {
index = SearchHashTable(archiveptr,filename,s_lcid);
if (index == 0xFFFFFFFF)
archiveptr = archiveptr->Next();
} while (archiveptr && (index == 0xFFFFFFFF));
if (!archiveptr) {
SErrSetLastError(SFILE_ERROR_FILE_NOT_FOUND);
return 0;
}
// DETERMINE THE BLOCK POINTER
BLOCKENTRYPTR block = archiveptr->blocktable+(archiveptr->hashtable+index)->block;
// CONFIRM THAT THIS IS A VALID FILE, AND IS NOT COMPRESSED OR ENCRYPTED.
// WE CAN'T RETURN WIN32 HANDLES TO COMPRESSED OR ENCRYPTED FILES.
if ((!block->sizefile) ||
(block->flags & (MPQ_COMPRESSEDMASK | MPQ_ENCRYPTED)) ||
!(block->flags & MPQ_ALLOCATED)) {
SErrSetLastError(SFILE_ERROR_FILE_INVALID);
return 0;
}
// OPEN THE ARCHIVE THAT CONTAINS THE FILE
*handle = CreateFile(archiveptr->name,
GENERIC_READ,
FILE_SHARE_READ,
(LPSECURITY_ATTRIBUTES)NULL,
OPEN_EXISTING,
FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
if (*handle == INVALID_HANDLE_VALUE)
return 0;
// SEEK TO THE BEGINNING OF THE FILE
SetFilePointer(*handle,block->offset,0,FILE_BEGIN);
return 1;
}
//===========================================================================
BOOL APIENTRY SFileReadFile (HSFILE handle,
LPVOID buffer,
DWORD bytestoread,
LPDWORD bytesread,
LPOVERLAPPED overlapped) {
if (bytesread)
*bytesread = 0;
VALIDATEBEGIN;
VALIDATE(buffer);
VALIDATEEND;
TRACEOUT(TRACEHANDLE,
"SFileReadFile(0x%x,0x%x,%u,*bytesread,0x%x)",
handle,buffer,bytestoread,overlapped);
// CHECK THE FILE HANDLE
FILEPTR fileptr;
if (!CheckFileHandle(handle,&fileptr))
return FALSE;
if (!bytestoread)
return TRUE;
// IF THIS HANDLE REPRESENTS A REAL FILE, FORWARD THE REQUEST TO THE
// STANDARD WIN32 API
if (fileptr->handle != INVALID_HANDLE_VALUE) {
DWORD localbytesread = 0;
HANDLE event = NULL;
if (overlapped) {
event = overlapped->hEvent;
overlapped->hEvent = NULL;
}
ReadFileChecked(fileptr->handle,
buffer,
bytestoread,
&localbytesread,
overlapped,
fileptr->name);
if (event)
SetEvent(event);
if (bytesread)
*bytesread = localbytesread;
return (localbytesread == bytestoread);
}
// TRY TO SATISFY, OR PARTIALLY SATISFY, THIS REQUEST FROM THE READ
// AHEAD BUFFER
if (fileptr->readaheadbuffer &&
(fileptr->readaheadoffset < fileptr->readaheadbytes) &&
!overlapped) {
DWORD satisfybytes = min(bytestoread,fileptr->readaheadbytes-fileptr->readaheadoffset);
CopyMemory(buffer,(LPBYTE)fileptr->readaheadbuffer+fileptr->readaheadoffset,satisfybytes);
fileptr->location += satisfybytes;
fileptr->readaheadoffset += satisfybytes;
buffer = (LPBYTE)buffer+satisfybytes;
bytestoread -= satisfybytes;
if (bytesread)
*bytesread = satisfybytes;
if (!bytestoread)
return TRUE;
}
// DETERMINE THE AMOUNT OF DATA TO READ
// (NO READ-AHEAD IS DONE FOR OVERLAPPED I/O, BECAUSE THE EVENT WILL
// BE TRIGGERED BEFORE WE HAVE A CHANCE TO DO THE BUFFER COPY)
LPVOID readbuffer = buffer;
DWORD readbytes = bytestoread;
if (!overlapped) {
DWORD desiredreadahead = min(READAHEAD,
fileptr->archive->sectorsize-(fileptr->location & (fileptr->archive->sectorsize-1)));
if (bytestoread < desiredreadahead) {
readbuffer = fileptr->readaheadbuffer;
readbytes = desiredreadahead;
}
}
// IF THERE IS NOTHING TO READ, RETURN
if (!readbytes)
return TRUE;
// IF THIS FILE IS ON A CD-ROM DRIVE, QUEUE THE REQUEST FOR THE CD-ROM
// I/O THREAD
DWORD location = overlapped ? overlapped->Offset : fileptr->location;
DWORD totalbytesread = 0;
if (fileptr->archive->cdrom != CDROM_FALSE) {
REQUESTPTR requestptr;
HANDLE event = overlapped ? overlapped->hEvent
: CreateEvent(NULL,TRUE,FALSE,NULL);
DWORD blocks = (readbytes+DATACHUNKSIZE-1)/DATACHUNKSIZE;
DWORD prevseq = 0;
DWORD loop;
for (loop = 0; loop < blocks; ++loop) {
BOOL lastblock = (loop == blocks-1);
DWORD offset = loop*DATACHUNKSIZE;
DWORD bytes = min(DATACHUNKSIZE,readbytes-offset);
requestptr = IssueRequest(fileptr,
location+loop*DATACHUNKSIZE,
(LPBYTE)readbuffer+offset,
NULL,
0,
NULL,
bytes,
INT_MAX,
prevseq,
lastblock ? event : NULL,
overlapped || !lastblock,
lastblock,
&prevseq);
if (!lastblock)
totalbytesread += bytes;
}
if (overlapped) {
SErrSetLastError(ERROR_IO_PENDING);
return FALSE;
}
WaitForSingleObject(event,INFINITE);
CloseHandle(event);
totalbytesread += requestptr->bytesread;
FREE(requestptr);
}
// OTHERWISE, READ THE REQUESTED DATA
else {
totalbytesread = InternalReadUnaligned(fileptr,
location,
readbuffer,
readbytes);
if (overlapped && overlapped->hEvent)
SetEvent(overlapped->hEvent);
}
// COPY THE READ DATA TO THE USER'S BUFFER
DWORD userbytesread = min(bytestoread,totalbytesread);
if (readbuffer != buffer) {
CopyMemory(buffer,readbuffer,userbytesread);
fileptr->readaheadoffset = userbytesread;
fileptr->readaheadbytes = totalbytesread;
}
// UPDATE THE FILE POINTER
if (!overlapped)
fileptr->location += userbytesread;
// RETURN THE NUMBER OF BYTES READ
if (bytesread)
*bytesread += userbytesread;
if (userbytesread == bytestoread)
return TRUE;
else {
SErrSetLastError(SFILE_ERROR_HANDLE_EOF);
return FALSE;
}
}
//===========================================================================
BOOL APIENTRY SFileSetBasePath (LPCTSTR path) {
VALIDATEBEGIN;
VALIDATE(path);
VALIDATEEND;
TRACEOUT(TRACEHANDLE,
"SFileSetBasePath(\"%s\")",
path);
// IF WE WERE GIVEN A NULL PATH, RESET THE BASE PATH VARIABLE. IT WILL
// BE AUTOMATICALLY SET ON NEXT USE TO THE PROGRAM PATH.
if (!*path)
s_basepath[0] = 0;
// OTHERWISE, SET THE BASE PATH TO THE GIVEN PATH
else {
BOOL terminated = (*(path+SStrLen(path)-1) == '\\');
if ((SStrLen(path)+1+!terminated) > MAX_PATH) {
SErrSetLastError(SFILE_ERROR_BAD_PATHNAME);
return 0;
}
SStrCopy(s_basepath,path,MAX_PATH);
if (!terminated)
SStrPack(s_basepath,"\\",MAX_PATH);
}
return TRUE;
}
//===========================================================================
DWORD APIENTRY SFileSetFilePointer (HSFILE handle,
LONG distancetomove,
PLONG distancetomovehigh,
DWORD movemethod) {
TRACEOUT(TRACEHANDLE,
"SFileSetFilePointer(0x%x,%d,0x%x,%u)",
handle,distancetomove,distancetomovehigh,movemethod);
if (distancetomovehigh && *distancetomovehigh) {
SErrSetLastError(SFILE_ERROR_INVALID_PARAMETER);
return 0xFFFFFFFF;
}
// CHECK THE FILE HANDLE
FILEPTR fileptr;
if (!CheckFileHandle(handle,&fileptr))
return 0xFFFFFFFF;
// IF THIS HANDLE REPRESENTS A REAL FILE, FORWARD THE REQUEST TO THE
// STANDARD WIN32 API
if (fileptr->handle != INVALID_HANDLE_VALUE)
return SetFilePointer(fileptr->handle,distancetomove,NULL,movemethod);
// SET THE NEW FILE LOCATION
switch (movemethod) {
case FILE_BEGIN:
fileptr->location = distancetomove;
break;
case FILE_CURRENT:
if ((distancetomove < 0) && (fileptr->location < (DWORD)-distancetomove))
fileptr->location = 0;
else
fileptr->location += distancetomove;
break;
case FILE_END:
if ((distancetomove < 0) && (fileptr->block->sizefile < (DWORD)-distancetomove))
fileptr->location = 0;
else
fileptr->location = fileptr->block->sizefile+distancetomove;
break;
}
fileptr->location = min(fileptr->location,fileptr->block->sizefile-1);
fileptr->readaheadoffset = 0;
fileptr->readaheadbytes = 0;
// RETURN THE NEW LOCATION
return fileptr->location;
}
//===========================================================================
BOOL APIENTRY SFileSetIoErrorMode (DWORD errormode,
SFILEERRORPROC errorproc) {
TRACEOUT(TRACEHANDLE,
"SFileSetIoErrorMode(%u,0x%x)",
errormode,errorproc);
s_ioerrormode = errormode;
s_ioerrorproc = errorproc;
return TRUE;
}
//===========================================================================
BOOL APIENTRY SFileSetLocale (LCID lcid) {
TRACEOUT(TRACEHANDLE,
"SFileSetLocale(0x%x)",
lcid);
s_lcid = lcid;
return 1;
}