#include "pch.hpp"
//==========================================================================//
// File:	 FileIO.cpp														//
// Project:  GameOS					                                        //
// Contents: file i/o routines												//
//--------------------------------------------------------------------------//
// Date      Who    Modification                                            //
// 11/10/97  JTR    Created													//
//																			//
//--------------------------------------------------------------------------//
// Copyright (C) 1999 Microsoft Corp.						                 //
// All Rights reserved worldwide                                            //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL              //
//==========================================================================//
#include "FileIO.hpp"
#include "ErrorHandler.hpp"
#include "MemoryManager.hpp"
#include "Windows.hpp"
#include "Time.hpp"

extern DWORD gLanguageDLL;

#ifdef LAB_ONLY
//
// Debugging information
//
char FileInfo[32][MAX_PATH+32];
DWORD CurrentFileInfo;
#endif

void StartFileThread();
extern HGOSHEAP CurrentHeap;
extern HGOSHEAP DefaultHeap;
extern __int64 CurrentTimeDate;
extern bool CurrentTimeDateValid;

DWORD NumberFilesOpen;

gosFileStream* ListOfFiles;

MemoryMappedFiles*	ListofMemoryMappedFiles;
DWORD NumberMemoryMappedFilesOpen;

DWORD BytesLoaded=0;

//
// Variables for file load thread
//
unsigned int FileThreadID=0;
HANDLE HandleFileThread;
CRITICAL_SECTION FileCriticalSection;
HANDLE FileEvent;
HANDLE KillFileEvent;


//
// DoesFileExist and ReadFile come through here
//
static bool RecursiveFileFunction = 0;








//
// No errors can occur (GameOS will prompt for retry or abort program)
//
// The whole file in read into memory, MemoryImage will point to the start of the file,
// Size will be the size of the file.
//
void __stdcall gos_GetFile( const char* FileName, BYTE** MemoryImage, DWORD* Size )
{
	char FilterName[MAX_PATH];
	gosASSERT( FileName && *FileName );
//
// Check for \\ characters
//
	if( _mbsstr( (const unsigned char*)FileName+1, (const unsigned char*)"\\\\") )
	{
		const char* Source=FileName;
		char* Dest=FilterName;
		*Dest++=*Source++;

		while( *Source )
		{
			if( *Source=='\\' && *(Source+1)=='\\' )
			{
				Source++;
			}
			*Dest++=*Source++;
		}
		*Dest=0;
		FileName=FilterName;
	}
//
// Check for being hooked
//
	if( !RecursiveFileFunction && Environment.HookGetFile )
	{
		RecursiveFileFunction=1;
		Environment.HookGetFile( FileName, MemoryImage, Size );
		SPEW(( GROUP_FILES, "gos_GetFile(\"%s\") Hooked - %dk",FileName,*Size/1024 ));
		RecursiveFileFunction=0;
		return;
	}

//
// Open file for reading
//
	#ifdef LAB_ONLY
	double TimeStamp=gos_GetHiResTime();
	#endif
	HANDLE hFile;
	do
	{
		hFile = CreateFile( FileName, GENERIC_READ, FILE_SHARE_READ,NULL, OPEN_EXISTING, NULL, NULL );
		
		if( hFile==INVALID_HANDLE_VALUE )
		{
			char Buffer[256];
			sprintf( Buffer, "File not found '%s'",FileName );
			if( ErrorHandler( gos_ErrorStop+gos_ErrorRetry+gos_ErrorNoRegisters+gos_ErrorPop2, Buffer ) )
				_asm int 3;
		}

	} while( hFile==INVALID_HANDLE_VALUE );
//
// Find length of file
//
	int Length=GetFileSize( hFile, NULL );
//
// Allocate a buffer for file
//
	if (Length > 0)
	{
		*MemoryImage=(BYTE*)gos_Malloc( Length, CurrentHeap );
//
// Read in whole file
//
	DWORD result=0;
	do
	{
		result=ReadFile( hFile, (void*)*MemoryImage, Length, Size, NULL );  

		if( 0==result )
		{
			char Buffer[256];
			sprintf( Buffer, gos_GetResourceString(gLanguageDLL, GOS_PROBLEM_READING_FILE), FileName );
			if( ErrorHandler( gos_ErrorStop+gos_ErrorRetry+gos_ErrorNoRegisters+gos_ErrorPop2, Buffer ) )
				_asm int 3;
		}

	} while( 0==result );
//
// Check whole file was read in
//
		#ifdef LAB_ONLY
		BytesLoaded+=*Size;
		#endif
		if( *Size!=Length )
			STOP(( gos_GetResourceString(gLanguageDLL, GOS_PROBLEM_READING_FILE), FileName ));
	}
	else
	{
		*MemoryImage = NULL;
		*Size = 0;
	}
//
// Close file handle and return
//
    CloseHandle( hFile );
//
// Debugging
//
	#ifdef LAB_ONLY
	char FileBuffer[128];
	if( strlen(FileName)<47 )
	{
		strcpy( FileBuffer, FileName );
	}
	else
	{
		strcpy( FileBuffer, "..." );
		strcat( FileBuffer, &FileName[strlen(FileName)-43] );
	}
	if( *Size < 1024 )
	{
		sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f GetFile \"%s\" %db",CurrentFrameNumber, TimeStamp, FileBuffer, *Size );
		SPEW(( GROUP_FILES, "gos_GetFile(\"%s\") - %db",FileName,*Size ));
	}
	else
	{
		sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f GetFile \"%s\" %dk",CurrentFrameNumber, TimeStamp, FileBuffer, *Size/1024 );
		SPEW(( GROUP_FILES, "gos_GetFile(\"%s\") - %dk",FileName,*Size/1024 ));
	}
	CurrentFileInfo=(CurrentFileInfo+1)&31;
	#endif
}





typedef struct _BGFile
{
	DWORD		Magic;
	_BGFile*	pNext;
	char		FileName[MAX_PATH];
	BYTE**		MemoryImage;
	BYTE*		pData;
	DWORD*		Size;
	DWORD		Offset;
	DWORD		MaxSize;
	bool		Loaded;					// Set when waiting to Close.

} BGFile;


BGFile* ListOfBackgroundFiles=0;
BGFile* ListOfLoadedFiles=0;

//
// File loading background thread
//
unsigned int __stdcall FileThread(void*)
{
	HANDLE	EventHandles[2];
	EventHandles[0]=KillFileEvent;
	EventHandles[1]=FileEvent;

    while( WaitForMultipleObjects( 2, EventHandles, FALSE, INFINITE) == WAIT_OBJECT_0+1 )		// Kill event will always be checked first
	{
		EnterCriticalSection( &FileCriticalSection );

		BGFile* pThis=ListOfBackgroundFiles;

		while( pThis )
		{
			if( !pThis->Loaded )
			{
//
// Leave critical section for slow loading of files
//
				LeaveCriticalSection( &FileCriticalSection );
//
// Open file for reading
//
				HANDLE hFile = CreateFile( pThis->FileName, GENERIC_READ, FILE_SHARE_READ,NULL, OPEN_EXISTING, NULL, NULL );
				if( hFile==INVALID_HANDLE_VALUE )
				{
					STOP(( gos_GetResourceString(gLanguageDLL, GOS_CANT_FIND_FILE),pThis->FileName ));
				}
//
// Find length of file
//
				int Length=GetFileSize( hFile, NULL );

//
// Factor in offset
//
				if( pThis->Offset != 0 )
				{
					if( Length < pThis->Offset )
						STOP(( "Asked for Offset %d into file %s length %d", pThis->Offset, pThis->FileName, Length ));
					Length -= pThis->Offset;
					SetFilePointer( hFile, pThis->Offset, NULL, FILE_BEGIN );
				}

//
// Factor in maximum size
//
				if(  Length > pThis->MaxSize )
				{
					Length = pThis->MaxSize;
				}
//
// Allocate a buffer for file
//
				BYTE* MemoryImage=(BYTE*)malloc( Length );				//OS malloc! - GameOS malloc is not thread safe
//
// Read in whole file
//
				ReadFile( hFile, MemoryImage, Length, pThis->Size, NULL );  
//
// Check whole file was read in
//
				if( *pThis->Size!=Length )
					STOP(( gos_GetResourceString(gLanguageDLL, GOS_PROBLEM_READING_FILE), pThis->FileName ));
//
// Close file handle
//
				CloseHandle( hFile );
//
// Update apps variables now file is in memory
//
				EnterCriticalSection( &FileCriticalSection );
				pThis->pData=MemoryImage;
				*pThis->MemoryImage=MemoryImage;
				pThis->Loaded=true;
			}
//
// Move to next in list
//
			pThis=pThis->pNext;
		}
//
// Now leave critical section before waitforsingleobject
//
		LeaveCriticalSection( &FileCriticalSection );
	}
	_endthreadex(0);
	return 0;
}



//
// Opens and reads in the whole file in a background thread. The MemoryImage pointer will be NULL until the file is read in completly.
//    When you are finished with the file, the Close function below must be called.
//
DWORD __stdcall gos_ReadFileInBackground( const char* FileName, BYTE** MemoryImage, DWORD* Size, DWORD Offset, DWORD MaxSize )
{
	#ifdef LAB_ONLY
	sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f ReadFileInBackground \"%s\"",CurrentFrameNumber, gos_GetHiResTime(), FileName );
	CurrentFileInfo=(CurrentFileInfo+1)&31;
	#endif
//
// Start up thread 1st time used
//
	StartFileThread();
//
// Create file reading data structure
//
	BGFile* pThis=(BGFile*)gos_Malloc( sizeof(BGFile) );
	pThis->Magic=0xeef546ad;
	pThis->pNext=0;
	gos_GetFullPathName( pThis->FileName, FileName );
	pThis->pData=0;
	*MemoryImage=0;
	*Size=0;
	pThis->MemoryImage=MemoryImage;
	pThis->Size=Size;
	pThis->Loaded=false;
//
// On Platform_DLL just do the load now (no thread exists)
//
	if( Platform==Platform_DLL )
	{
		gos_GetFile( FileName, MemoryImage, Size );
		pThis->pData=*MemoryImage;
		pThis->Loaded=true;
		return (DWORD)pThis;
	}
//
// Add file to end of list
//
	EnterCriticalSection( &FileCriticalSection );

	if( 0==ListOfBackgroundFiles )
	{
		ListOfBackgroundFiles=pThis;
	}
	else
	{
		BGFile* pTemp=ListOfBackgroundFiles;
		BGFile* pLast=0;

		while( pTemp )
		{
			pLast=pTemp;
			pTemp=pTemp->pNext;
		}
		
		pLast->pNext=pThis;
	}

	LeaveCriticalSection( &FileCriticalSection );
//
// Trigger read thread to wake up and read file
//
	SetEvent( FileEvent );
	return (DWORD)pThis;
}




//
// Closes and releases the memory used by a file read in the background. (Error if not read in yet)
//
void __stdcall gos_CloseBackgroundFile( DWORD Handle )
{
	BGFile* pThis=(BGFile*)Handle;
	#ifdef LAB_ONLY
	sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f CloseBackgroundFile \"%s\"",CurrentFrameNumber, gos_GetHiResTime(), pThis->FileName );
	CurrentFileInfo=(CurrentFileInfo+1)&31;
	#endif
//
// Check handle valid
//
	if( 0==pThis || pThis->Magic!=0xeef546ad )	
		STOP(( "Invalid file handle" ));
//
// Error if file not read yet
//
	if( !pThis->Loaded )
	{
		STOP(( "Closing an unread file" ));
	}
//
// On Platform_DLL just free the memory
//
	if( Platform==Platform_DLL )
	{
		if( pThis==ListOfBackgroundFiles )
		{
			ListOfBackgroundFiles=pThis->pNext;
			if( pThis->pData )
			{
				gos_Free( pThis->pData );
			}
			gos_Free( pThis );
		}
		else
		{
			BGFile* pTemp=ListOfBackgroundFiles;

			while( pTemp )
			{
				if( pTemp->pNext==pThis )
				{
					pTemp->pNext=pThis->pNext;
					if( pThis->pData )
					{
						gos_Free( pThis->pData );
					}
					gos_Free( pThis );
					break;
				}
				pTemp=pTemp->pNext;
			}
		}
		return;
	}
//
// Find and delete file in list (and reclaim memory)
//
	EnterCriticalSection( &FileCriticalSection );

	if( pThis==ListOfBackgroundFiles )
	{
		ListOfBackgroundFiles=pThis->pNext;
		if( pThis->pData )
		{
			free( pThis->pData );
		}
		gos_Free( pThis );
	}
	else
	{
		BGFile* pTemp=ListOfBackgroundFiles;

		while( pTemp )
		{
			if( pTemp->pNext==pThis )
			{
				pTemp->pNext=pThis->pNext;
				if( pThis->pData )
				{
					free( pThis->pData );
				}
				gos_Free( pThis );
				break;
			}
			pTemp=pTemp->pNext;
		}
	}

	LeaveCriticalSection( &FileCriticalSection );
}











//
// Init the file system
//
void Init_FileSystem()
{
//
// Clear out variables
//
	NumberMemoryMappedFilesOpen=0;
	ListofMemoryMappedFiles=0;
	NumberFilesOpen=0;
	ListOfFiles=0;
//
// Clear debugging information
//
	#ifdef LAB_ONLY
	for( int t0=0; t0<32; t0++ )
		FileInfo[t0][0]=0;
	CurrentFileInfo=0;
	#endif
//
// Create data structure for the file loading background thread
//
	HandleFileThread=0;
	ListOfLoadedFiles=0;
	ListOfBackgroundFiles=0;
}

extern void gosSetThreadName(DWORD dwThreadID, char *szThreadName);


void StartFileThread()
{
	if( 0==HandleFileThread && Platform!=Platform_DLL )
	{
		InitializeCriticalSection( &FileCriticalSection );
		FileEvent=CreateEvent( NULL, false, false, NULL );
		KillFileEvent=CreateEvent( NULL, false, false, NULL );
		HandleFileThread=(HANDLE)_beginthreadex( 0, 0, FileThread, 0, 0, &FileThreadID );
//
// Name thread
//
		gosSetThreadName( FileThreadID, "FileLoad" );
	}
}


//
// Close down file system and look for leaks
//
void Destory_FileSystem( bool NoErrors )
{
//
// Clean up memory mapped files
//
	MemoryMappedFiles* pThis=ListofMemoryMappedFiles;
	while( pThis )
	{
		if( !NoErrors )
		{
			PAUSE(( "Memory mapped file '%s' is still open", pThis->Name ));
		}
		UnmapViewOfFile( pThis->pFile );
		CloseHandle( pThis->hFileMapping );
		CloseHandle( pThis->hFile );
		pThis->Magic=0;
		MemoryMappedFiles* pTemp=pThis;
		pThis=pThis->pNext;
		gos_Free( pTemp );
	}
	NumberMemoryMappedFilesOpen=0;
	ListofMemoryMappedFiles=0;
//
// Clean up regular files
//
	{
		gosFileStream* pThis=ListOfFiles;
		while( pThis )
		{
			if( !NoErrors )
			{
				PAUSE(( "File '%s' is still open", pThis->m_Filename ));
			}
			CloseHandle( pThis->m_hFile );
			gosFileStream* pTemp=pThis;
			pThis=pThis->pNext;
			gos_Free( pTemp );
		}
	}
	NumberFilesOpen=0;
	ListOfFiles=0;
//
// Close down file reading thread
//
	if( HandleFileThread )
	{
        SetEvent( KillFileEvent );
        WaitForSingleObject( HandleFileThread, INFINITE );
		CloseHandle( HandleFileThread );
		DeleteCriticalSection( &FileCriticalSection );
		CloseHandle( FileEvent );
		CloseHandle( KillFileEvent );
		HandleFileThread=0;
	}
//
// Free any memory in list
//
	{
		BGFile* pThis=ListOfBackgroundFiles;
		while( pThis )
		{
			if( !NoErrors )
			{
				PAUSE(( "Background file '%s' never closed", pThis->FileName ));
			}
			if( pThis->pData )
			{
				if( Platform!=Platform_DLL )
					free( pThis->pData );
				else
					gos_Free( pThis->pData );
			}
			BGFile* pTemp=pThis;
			pThis=pThis->pNext;
			gos_Free( pTemp );
		}
		ListOfBackgroundFiles=0;
	}		
}



//
// No errors can occur (GameOS will prompt for retry or abort program)
//
// The whole file in read into memory, MemoryImage will point to the start of the file,
// Size will be the size of the file.
//
DWORD __stdcall gos_OpenMemoryMappedFile( const char* FileName, BYTE** MemoryImage, DWORD* Size )
{
	#ifdef LAB_ONLY
	sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f OpenMemoryMappedFile \"%s\"",CurrentFrameNumber, gos_GetHiResTime(), FileName );
	CurrentFileInfo=(CurrentFileInfo+1)&31;
	#endif
	SPEW(( GROUP_FILES, "gos_OpenMemoryMappedFile(\"%s\")",FileName ));
//
// Create a structure for all variables
//
	MemoryMappedFiles* pThis=(MemoryMappedFiles*)gos_Malloc( sizeof(MemoryMappedFiles), DefaultHeap );
//
// Get absolute path name
//
	gos_GetFullPathName( pThis->Name, FileName );
//
// Check for duplicate opens - increase reference count
//
	MemoryMappedFiles* pTemp=ListofMemoryMappedFiles;
	while( pTemp )
	{
		if( 0==stricmp( pTemp->Name, pThis->Name ) )
		{
			gos_Free( pThis );
			pTemp->RefCount++;
			*Size=pTemp->Size;
			*MemoryImage=pTemp->pFile;
			return (DWORD)pTemp;
		}
		pTemp=pTemp->pNext;
	}
//
// Open memory mapped file for reading
//
	do
	{
		pThis->hFile = CreateFile( FileName, GENERIC_READ, FILE_SHARE_READ,NULL, OPEN_EXISTING, NULL, NULL );
		
		if( pThis->hFile==INVALID_HANDLE_VALUE )
		{
			char Buffer[256];
			sprintf( Buffer, gos_GetResourceString(gLanguageDLL, GOS_FILE_NOT_FOUND),FileName );
			if( ErrorHandler( gos_ErrorStop+gos_ErrorRetry+gos_ErrorNoRegisters+gos_ErrorPop2, Buffer ) )
				_asm int 3;
		}

	} while( pThis->hFile==INVALID_HANDLE_VALUE );
//
// Find length of file
//
	pThis->Size=GetFileSize( pThis->hFile, NULL );
//
// Map file into memory
//
	pThis->hFileMapping=CreateFileMapping( pThis->hFile, NULL, PAGE_READONLY, 0, 0, NULL );
	if( pThis->hFileMapping==NULL )
	{
		STOP(( gos_GetResourceString(gLanguageDLL, GOS_CANT_MAP_FILE), FileName ));
	}
//
// Get pointer to base
//
	pThis->pFile=(BYTE*)MapViewOfFile( pThis->hFileMapping, FILE_MAP_READ, 0, 0, 0 );
	if( pThis->pFile==NULL )
	{
		STOP(( gos_GetResourceString(gLanguageDLL, GOS_CANT_MAP_FILE), FileName ));
	}
//
// Setup other variables
//
	pThis->Magic=0x23AA7812;
	pThis->RefCount=1;
	pThis->pNext=ListofMemoryMappedFiles;
	*MemoryImage=pThis->pFile;
	*Size=pThis->Size;
	ListofMemoryMappedFiles=pThis;
	NumberMemoryMappedFilesOpen++;
//
// Return pointer to structure
//
	return (DWORD)pThis;
}


//
// Close a memory mapped file
//
void __stdcall gos_CloseMemoryMappedFile( DWORD Handle )
{
	MemoryMappedFiles* pThis=(MemoryMappedFiles*)Handle;
//
// Make sure handle is valid
//
	if( pThis->Magic!=0x23AA7812 )
	{
		STOP(( "Invalid file handle '%s'", pThis->Name ));
	}
//
	SPEW(( GROUP_FILES, "gos_CloseMemoryMappedFile(\"%s\")",pThis->Name ));
//
// If opened more than once, don't close file
//
	pThis->RefCount--;
	if(	pThis->RefCount!=0 )
		return;
//
// CLose memory mapped file
//
	UnmapViewOfFile( pThis->pFile );
	CloseHandle( pThis->hFileMapping );
	CloseHandle( pThis->hFile );
	NumberMemoryMappedFilesOpen--;
//
// Remove from chain
//
	if( ListofMemoryMappedFiles==pThis )
	{
		ListofMemoryMappedFiles=pThis->pNext;
		gos_Free(pThis);
	}
	else
	{
		MemoryMappedFiles* pLast=0;
		MemoryMappedFiles* pTemp=ListofMemoryMappedFiles;
		while( pTemp )
		{
			pLast=pTemp;
			pTemp=pTemp->pNext;
			if( pTemp==pThis )
			{
				pLast->pNext=pThis->pNext;
				pThis->Magic=0;
				gos_Free(pThis);
				return;
			}
		}
	}
	return;
}






//
// Gets a list of valid drives, returned as a list of strings, ending in 0,0
//
// eg:  "a:\","c:\","d:\","",""
//
// Returns length of string
//
DWORD __stdcall gos_GetValidDrives( char* Buffer, DWORD buf_len )
{
	SPEW(( GROUP_FILES, "gos_GetValidDrives()" ));
	return GetLogicalDriveStrings( buf_len, Buffer );
}


//
// Returns the drive label for a root directory specified. eg:  "c:\" might return "DriveC"
//
bool __stdcall gos_GetDriveLabel( const char* RootName, char* DriveLabel, DWORD DriveLabelBufferLen )
{
	SPEW(( GROUP_FILES, "gos_GetDriveLabel()" ));
	DWORD snum,maxcom,sysflags;
	return GetVolumeInformation( RootName, DriveLabel, DriveLabelBufferLen, &snum, &maxcom, &sysflags, NULL, 0 ) ? true:false;
}




//
// Get the space available on the drive specified, either "x:" or "x:\"
//
__int64 __stdcall gos_GetDriveFreeSpace( char* Path )
{
	gosASSERT( Path && ((strlen(Path)==2) || (strlen(Path)==3)) && Path[1]==(char)':' );
	SPEW(( GROUP_FILES, "gos_GetDriveFreeSpace()" ));
	char Buffer[3];
	Buffer[0]=Path[0];
	Buffer[1]=':';
	Buffer[2]=0;

	if( _GetDiskFreeSpaceEx )
	{
		ULARGE_INTEGER Avail, Free, Total;
		_GetDiskFreeSpaceEx( Buffer, &Avail, &Free, &Total );
		return *(__int64*)&Avail;
	}

	DWORD Sectors,Bytes,Clusters,Totals;
	GetDiskFreeSpace( Buffer, &Sectors, &Bytes, &Clusters, &Totals );
	return Sectors*Bytes*Clusters;
}


//
// Gets the current path
//
void __stdcall gos_GetCurrentPath( char* Buffer,int buf_len )
{
	SPEW(( GROUP_FILES, "gos_GetCurrentPath()" ));
	GetCurrentDirectory( buf_len, Buffer );
}



//
// Used in ToolOS.hpp
//
// Create directory. - Returns TRUE for sucess
//
bool __stdcall gos_CreateDirectory( const char* FileName )
{
	#ifdef LAB_ONLY
	sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f CreateDirectory \"%s\"",CurrentFrameNumber, gos_GetHiResTime(), FileName );
	CurrentFileInfo=(CurrentFileInfo+1)&31;
	#endif
	SPEW(( GROUP_FILES, "gos_CreateDirectory(%s)",FileName ));
	return CreateDirectory( FileName, NULL )!=0;
}

//
// Used in ToolOS.hpp
//
// Rename file/directory. - Returns TRUE for sucess
//
bool __stdcall gos_RenameFile( const char* FileNameFrom, const char* FileNameTo )
{
	#ifdef LAB_ONLY
	sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f RenameFile \"%s\" to \"%s\"",CurrentFrameNumber, gos_GetHiResTime(), FileNameFrom, FileNameTo );
	CurrentFileInfo=(CurrentFileInfo+1)&31;
	#endif
	SPEW(( GROUP_FILES, "gos_RenameFile(%s to %s)",FileNameFrom, FileNameTo ));
	return MoveFile( FileNameFrom, FileNameTo )!=0;
}

//
// Used in ToolOS.hpp
//
// Delete file. - Returns TRUE for sucess
//
bool __stdcall gos_DeleteFile( const char* FileName )
{
	#ifdef LAB_ONLY
	sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f DeleteFile \"%s\"",CurrentFrameNumber, gos_GetHiResTime(), FileName );
	CurrentFileInfo=(CurrentFileInfo+1)&31;
	#endif
	SPEW(( GROUP_FILES, "gos_DeleteFile(%s)",FileName ));
	return DeleteFile( FileName )!=0;
}

//
// Used in ToolOS.hpp
//
// Delete directory. - Returns TRUE for sucess  (Directories must be empty)
//
bool __stdcall gos_DeleteDirectory( const char* DirectoryName )
{
	#ifdef LAB_ONLY
	sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f DeleteDirectory \"%s\"",CurrentFrameNumber, gos_GetHiResTime(), DirectoryName );
	CurrentFileInfo=(CurrentFileInfo+1)&31;
	#endif
	SPEW(( GROUP_FILES, "gos_DeleteDirectory(%s)",DirectoryName ));
	return RemoveDirectory( DirectoryName )!=0;
}

WIN32_FIND_DATA		FindFileData;
HANDLE				FindFileHandle=0;
//
// Used in ToolOS.hpp
//
// Find files matching pattern - returns NULL when no more files (Can be used too see if a directory is empty : ie: 0==gos_FindFiles( "\\graphics", 1 )
//
char* __stdcall gos_FindFiles( const char* FileName )
{
	#ifdef LAB_ONLY
	sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f FindFiles \"%s\"",CurrentFrameNumber, gos_GetHiResTime(), FileName );
	CurrentFileInfo=(CurrentFileInfo+1)&31;
	#endif
	SPEW(( GROUP_FILES, "gos_FindFiles(%s)",FileName ));

	FindFileHandle=FindFirstFile( FileName, &FindFileData );

	if( INVALID_HANDLE_VALUE==FindFileHandle )
	{
		FindFileHandle=0;
		return NULL;
	}

	while( FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
	{
		if( 0==FindNextFile( FindFileHandle, &FindFileData ) )
		{
			FindClose( FindFileHandle );
			FindFileHandle=0;
			return NULL;
		}
	}

	return FindFileData.cFileName;
}
//
// Continues the previous gos_FindFiles
//
char* __stdcall gos_FindFilesNext()
{
	#ifdef LAB_ONLY
	sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f FindFilesNext",CurrentFrameNumber, gos_GetHiResTime() );
	CurrentFileInfo=(CurrentFileInfo+1)&31;
	#endif
	SPEW(( GROUP_FILES, "gos_FindFilesNext()" ));

	if( FindFileHandle )
	{
		if( FindNextFile( FindFileHandle, &FindFileData ) )
		{
			while( FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
			{
				if( 0==FindNextFile( FindFileHandle, &FindFileData ) )
				{
					FindClose( FindFileHandle );
					FindFileHandle=0;
					return NULL;
				}
			}
			return FindFileData.cFileName;
		}
		else
		{
			FindClose( FindFileHandle );
			FindFileHandle=0;
			return NULL;
		}
	}
	else
	{
		return NULL;
	}
}
//
// Stop the previous gos_FindFiles
//
void __stdcall gos_FindFilesClose()
{
	#ifdef LAB_ONLY
	sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f FindFilesClose",CurrentFrameNumber, gos_GetHiResTime() );
	CurrentFileInfo=(CurrentFileInfo+1)&31;
	#endif
	SPEW(( GROUP_FILES, "gos_FindFilesClose()" ));
	if( FindFileHandle )
		FindClose( FindFileHandle );
	FindFileHandle=0;
}

WIN32_FIND_DATA		FindDirectoryData;
HANDLE				FindDirectoryHandle=0;
//
// Used in ToolOS.hpp
//
// Find directories matching pattern - returns NULL when no more directories (Does not return . or ..)
//
char* __stdcall gos_FindDirectories( const char* DirectoryName )
{
	#ifdef LAB_ONLY
	sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f FindDirectories \"%s\"",CurrentFrameNumber, gos_GetHiResTime(), DirectoryName );
	CurrentFileInfo=(CurrentFileInfo+1)&31;
	#endif
	SPEW(( GROUP_FILES, "gos_FindDirectories(%s)",DirectoryName ));

	FindDirectoryHandle=FindFirstFile( DirectoryName, &FindDirectoryData );

	if( INVALID_HANDLE_VALUE==FindDirectoryHandle )
	{
		FindDirectoryHandle=0;
		return NULL;
	}

	if( 0==strcmp( FindDirectoryData.cFileName, "." ) || 0==strcmp( FindDirectoryData.cFileName, ".." ) ) 
		FindDirectoryData.dwFileAttributes=0;

	while( 0==(FindDirectoryData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) )
	{
		if( 0==FindNextFile( FindDirectoryHandle, &FindDirectoryData ) )
		{
			FindClose( FindDirectoryHandle );
			FindDirectoryHandle=0;
			return NULL;
		}
	
		if( 0==strcmp( FindDirectoryData.cFileName, "." ) || 0==strcmp( FindDirectoryData.cFileName, ".." ) ) 
			FindDirectoryData.dwFileAttributes=0;
	}

	return FindDirectoryData.cFileName;
}

//
// Continues the previous gos_FindDirectoriesNext (Does not return . or ..)
//
char* __stdcall gos_FindDirectoriesNext()
{
	#ifdef LAB_ONLY
	sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f FindDirectoriesNext",CurrentFrameNumber, gos_GetHiResTime() );
	CurrentFileInfo=(CurrentFileInfo+1)&31;
	#endif
	SPEW(( GROUP_FILES, "gos_FindDirectoriesNext()" ));

	if( FindDirectoryHandle )
	{
		if( FindNextFile( FindDirectoryHandle, &FindDirectoryData ) )
		{
			if( 0==strcmp( FindDirectoryData.cFileName, "." ) || 0==strcmp( FindDirectoryData.cFileName, ".." ) ) 
				FindDirectoryData.dwFileAttributes=0;

			while( 0==(FindDirectoryData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) )
			{
				if( 0==FindNextFile( FindDirectoryHandle, &FindDirectoryData ) )
				{
					FindClose( FindDirectoryHandle );
					FindDirectoryHandle=0;
					return NULL;
				}

				if( 0==strcmp( FindDirectoryData.cFileName, "." ) || 0==strcmp( FindDirectoryData.cFileName, ".." ) ) 
					FindDirectoryData.dwFileAttributes=0;
			}
			return FindDirectoryData.cFileName;
		}
		else
		{
			FindClose( FindDirectoryHandle );
			FindDirectoryHandle=0;
			return NULL;
		}
	}
	else
	{
		return NULL;
	}
}

//
// Used in ToolOS.hpp
//
// Stop the previous gos_FindDirectories
//
void __stdcall gos_FindDirectoriesClose()
{
	#ifdef LAB_ONLY
	sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f FindDirectoriesClose",CurrentFrameNumber, gos_GetHiResTime() );
	CurrentFileInfo=(CurrentFileInfo+1)&31;
	#endif
	SPEW(( GROUP_FILES, "gos_FindDirectoriesClose()" ));
	if( FindDirectoryHandle )
		FindClose( FindDirectoryHandle );
	FindDirectoryHandle=0;
}

//
// Used in ToolOS.hpp
//
// Return full path name of file - FullPath should point to a 256 byte buffer
//
void __stdcall gos_GetFullPathName( char* FullPath, const char* FileName )
{
	SPEW(( GROUP_FILES, "gos_GetFullPathName()" ));
	LPTSTR temp;
	GetFullPathName( FileName, 256, FullPath, &temp );
}

WIN32_FIND_DATA TempFindData;
//
// Used in ToolOS.hpp
//
// Get file size information (-1 if error)
//
DWORD __stdcall gos_FileSize( const char* FileName )
{
	#ifdef LAB_ONLY
	sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f FileSize \"%s\"",CurrentFrameNumber, gos_GetHiResTime(), FileName );
	CurrentFileInfo=(CurrentFileInfo+1)&31;
	#endif
	SPEW(( GROUP_FILES, "gos_FileSize(%s)",FileName ));

	if( _GetFileAttributesEx )
	{
		WIN32_FILE_ATTRIBUTE_DATA Info;
		if( _GetFileAttributesEx( FileName, GetFileExInfoStandard, &Info ) )
			return Info.nFileSizeLow;
	}
	else
	{
		HANDLE Handle=FindFirstFile( FileName, &TempFindData );
		if( INVALID_HANDLE_VALUE!=Handle )
		{
			FindClose( Handle );
			return TempFindData.nFileSizeLow;
		}
	}
	return -1;
}

//
// Used in ToolOS.hpp
//
// Get file date/time information (-1 if error) - this can be compared directly, and decoded using gos_FileTimeString
//
__int64 __stdcall gos_FileTimeStamp( const char* FileName )
{
	#ifdef LAB_ONLY
	sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f FileTimeStamp \"%s\"",CurrentFrameNumber, gos_GetHiResTime(), FileName );
	CurrentFileInfo=(CurrentFileInfo+1)&31;
	#endif
	SPEW(( GROUP_FILES, "gos_FileTimeStamp(%s)",FileName ));

	if( _GetFileAttributesEx )
	{
		WIN32_FILE_ATTRIBUTE_DATA Info;
		if( _GetFileAttributesEx( FileName, GetFileExInfoStandard, &Info ) )
			return *(__int64*)&Info.ftLastWriteTime;
	}
	else
	{
		HANDLE Handle=FindFirstFile( FileName, &TempFindData );
		if( INVALID_HANDLE_VALUE!=Handle )
		{
			FindClose( Handle );
			return *(__int64*)&TempFindData.ftLastWriteTime;
		}
	}
	return -1;
}

char* DayString[]={
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
};

//
// Used in ToolOS.hpp
//
// Get the current date/time information (only update once per frame - some apps call this 10,000 time during init!!!)
//
__int64 __stdcall gos_GetTimeDate()
{
	if( !CurrentTimeDateValid )
	{
		CurrentTimeDateValid=1;	
		FILETIME ft;
		GetSystemTimeAsFileTime( &ft );
		CurrentTimeDate=*(__int64*)&ft;
	}
	SPEW(( GROUP_FILES, "gos_GetTimeDate()" ));
	return CurrentTimeDate;
}


//
// Returns an ASCII time/date string from a file time
//
char* __stdcall gos_FileTimeString( __int64 Time )
{
	SPEW(( GROUP_FILES, "gos_FileTimeString()" ));
	static char Buffer[64];
	FILETIME lft;
	SYSTEMTIME st;
	FileTimeToLocalFileTime( (FILETIME*)&Time, &lft );	// Convert to local time
	FileTimeToSystemTime( &lft,&st );					// Convert to system time
	wsprintf( Buffer, "%02d:%02d:%02d %s %d/%d/%d", st.wHour, st.wMinute, st.wSecond, DayString[st.wDayOfWeek], st.wMonth, st.wDay, st.wYear );
	return Buffer;
}

//
// Used in ToolOS.hpp
//
// Get file read only attribute information
//
bool __stdcall gos_FileReadOnly( const char* FileName )
{
	#ifdef LAB_ONLY
	sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f FileReadOnly \"%s\"",CurrentFrameNumber, gos_GetHiResTime(), FileName );
	CurrentFileInfo=(CurrentFileInfo+1)&31;
	#endif
	SPEW(( GROUP_FILES, "gos_FileReadOnly(%s)",FileName ));

	DWORD Attrib=GetFileAttributes( FileName );
	if( Attrib==-1 )
		return FALSE;
	else
		return (Attrib&FILE_ATTRIBUTE_READONLY);
}

//
// Used in ToolOS.hpp
//
// Set file to read/write
//
void __stdcall gos_FileSetReadWrite( const char* FileName )
{
	#ifdef LAB_ONLY
	sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f FileSetReadWrite \"%s\"",CurrentFrameNumber, gos_GetHiResTime(), FileName );
	CurrentFileInfo=(CurrentFileInfo+1)&31;
	#endif
	SPEW(( GROUP_FILES, "gos_FileSetReadWrite(%s)",FileName ));
	SetFileAttributes( FileName, FILE_ATTRIBUTE_NORMAL);
}

//
// Used in ToolOS.hpp
//
// Set file to read only
//
void __stdcall gos_FileSetReadOnly( const char* FileName )
{
	#ifdef LAB_ONLY
	sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f FileSetReadOnly \"%s\"",CurrentFrameNumber, gos_GetHiResTime(), FileName );
	CurrentFileInfo=(CurrentFileInfo+1)&31;
	#endif
	SPEW(( GROUP_FILES, "gos_FileSetReadOnly(%s)",FileName ));
	SetFileAttributes( FileName, FILE_ATTRIBUTE_READONLY);
}








//
// Compress/ Decompress variables
//
static DWORD 	InBufferUpperLimit = 0;
static DWORD 	InBufferPos = 0;	
static BYTE*	InBuffer = NULL;
static DWORD 	OutBufferPos = 0;
static DWORD	MaxOutBuffer = 0;
static BYTE*	OutBuffer = NULL;
static DWORD 	PrefixCode = 0;
static DWORD 	FreeCode = 0;
static DWORD 	MaxCode = 0;
static DWORD 	NBits = 0;
static DWORD 	BitOffset = 0;
static BYTE 	K = 0;
static DWORD	TempSP;

typedef struct
{
	DWORD hashFirst;
	DWORD hashNext;
	unsigned char hashChar;
} Comp_HashStruc;

#define MaxMax			4096
#define HASH_CLEAR		256						// clear hash table command code
#define HASH_EOF        257						// End Of Data command code
#define HASH_FREE       258						// First Hash Table Chain Offset Value
#define BASE_BITS       9
#define MAX_BIT_INDEX   (1 << BASE_BITS)

typedef struct 
{
	unsigned short Chain;
	unsigned char Suffix;
} Decomp_HashStruct;

static Decomp_HashStruct*	LZOldChain = NULL;	// Old Chain Value Found
static Decomp_HashStruct*	LZChain = NULL;		// Current Chain Value Found 
static DWORD		LZMaxIndex = 0;				// Max index value in Hash Table
static DWORD		LZCodeMask = 0;
static DWORD		LZFreeIndex = 0;			// Current Free index into Hash Table
static BYTE*		LZSrcBufEnd = NULL;			// ptr to 3rd from last byte in src buffer
static BYTE				LZOldSuffix = 0;		// Current Suffix Value found

//
// 50K buffer for decompress/compress
//
static BYTE				LZHashBuffer[sizeof(Comp_HashStruc) * MaxMax + 1024];

//
//
// LZ Compression - returns length of compressed destination buffer
//
//
DWORD __stdcall gos_LZCompress( BYTE* dest, BYTE* src, DWORD srcLen, DWORD destLen )
{
	SPEW(( GROUP_FILES, "gos_LZCompress(%d bytes)",srcLen ));

	DWORD result = 0;
	DWORD clearSize = sizeof(Comp_HashStruc) * 256;

	__asm
	{
		mov		TempSP,esp

		cmp		destLen,0
		jnz		over1
		mov		destLen,0x40000000		// Add 1Gig as default largest size
over1:
		mov		eax,[dest]
		mov		OutBufferPos,eax
		mov		OutBuffer,eax
		add		eax,destLen
		sub		eax,3					// Routine often writes 3 bytes at once
		mov		MaxOutBuffer,eax

		mov		eax,[src]
		mov		[InBufferPos],eax
		mov		[InBuffer],eax
		add		eax,[srcLen]
		mov		[InBufferUpperLimit],eax

		mov		[BitOffset],0 			// zero out BitOffset
		
		mov		[NBits],9  				// Starting with 9 bit codes
		mov		[MaxCode],512  			// 10 0000 0000 b
		mov		eax,-1  				// Marked as unused
		mov		ecx,[clearSize]			// Clear first 256 entries
		mov		edi,offset LZHashBuffer	// Start at the start of buffer
		rep		stosb
		mov		[FreeCode],HASH_FREE 	// Next code to use is first_free

		mov		eax,HASH_CLEAR 			// first byte in buffer or file is CLEAR
		
		xor		edx,edx					// make sure the DL is CLEAR
		mov		edi,[OutBufferPos] 		// obtain destination address

		cmp		edi,MaxOutBuffer
		jnc		overrun

		mov		ecx,[BitOffset]  		// get bitposition
		jecxz	save1

shift1:
		shl		ax,1
		rcl		edx,1
		loop	shift1
		or		al,[edi]
save1:
		stosw
		mov		al,dl
		stosb
		
		mov		ecx,[NBits]				// get number of bits to advance
		mov		eax,[BitOffset]			// get low word of OutBuffer
		add		eax,ecx
		mov		cl,al
		shr		al,3
		add		[OutBufferPos],eax
		and		cl,7
		movzx	ecx,cl
		mov		[BitOffset],ecx

//-------------------------------------------------------------------------
// This is the main compression loop     
//-------------------------------------------------------------------------

		mov		esi,[InBufferPos]  		// get address
 		cmp		esi,[InBufferUpperLimit]// Check to see if we are done
		cmc								// compliment carry
		jc		doneRC1

		lodsb 
		mov		[InBufferPos],esi
		clc

doneRC1:
Make_Into_Code:

		movzx	eax,al   				// turn char into code
		
Set_AX_Prefix:

		mov		[PrefixCode],eax 		// into prefix code
		
		mov		esi,[InBufferPos]  		// get address
 		cmp		esi,[InBufferUpperLimit]// Check to see if we are done
		cmc								// compliment carry
		jc		doneRC2

		lodsb
		mov		[InBufferPos],esi
		clc

doneRC2:

		jc		FoundEOF  				// No more input
		mov		[K],al  				// Save returned char
		mov		ebx,[PrefixCode] 		// check for this pair

		lea		esi,[ebx*8]
		add		esi,ebx  				// EBX * 9
		add		esi,offset LZHashBuffer
		
		xor		edi,edi					// flag destination
		cmp		[esi].hashFirst,-1 		// see if code is used
		je		short exit1  			// if == then CARRY = CLEAR, set it  
		inc		edi    					// flag as used
		mov		ebx,[esi].hashFirst		// get prefix code

lookloop:

		lea		esi,[ebx*8]
		add		esi,ebx  				// EBX * 9
		add		esi,offset LZHashBuffer
		
		cmp		[esi].hashChar,al  		// is the suffix the same? if yes
		jne		notSame  				// then we can compress this a
		clc    							// little more by taking this prefix
		mov		eax,ebx					// code and getting a new suffix
		jmp		short exit2   			// in a moment

notSame:

		cmp		[esi].hashNext,-1 		// found a new pattern so exit
		je		exit1    				// if == then CARRY = CLEAR, set it
		mov		ebx,[esi].hashNext 		// continue through chain to get to
		jmp		short lookloop			// end of chain

exit1:

		stc
exit2:
		jnc		Set_AX_Prefix 			// new prefix in EAX
		
		mov		ebx,[FreeCode] 			// get the next available hash table code
		push	ebx
		or		edi,edi   				// is this first use of prefix?
		je		short newprefix
		mov		[esi].hashNext,ebx 		// if not, then set next code dest
		jmp		short addcode

newprefix:

		mov		[esi].hashFirst,ebx		// Mark first as used

addcode:

		cmp		ebx,MaxMax				// is this the last code?
		je		exitAC

		lea		esi,[ebx*8]
		add		esi,ebx					// EBX * 9
		add		esi,offset LZHashBuffer
			
		mov		[esi].hashFirst,-1		// mark new entry as unused
		mov		[esi].hashNext,-1
		mov		[esi].hashChar,al		// and save suffix
		inc		ebx						// set up for next code available

exitAC:
		mov		[FreeCode],ebx
		pop		ebx

		push	ebx						// save new code
		mov		eax,[PrefixCode]		// write the old prefix code
		
		xor		edx,edx					// make sure the DL is CLEAR
		mov		edi,[OutBufferPos]   	// obtain destination address

		cmp		edi,MaxOutBuffer
		jnc		overrun

		mov		ecx,[BitOffset]        	// get bitposition
		jecxz	save5
		
shift5:

		shl		ax,1
		rcl		edx,1
		loop	shift5
		or		al,[edi]

save5:
		stosw
		mov		al,dl
		stosb

		mov		ecx,[NBits]				// get number of bits to advance
		mov		eax,[BitOffset]			// get low word of OutBuffer
		add		eax,ecx
		mov		cl,al
		shr		al,3
		add		[OutBufferPos],eax
		and		cl,7
		movzx	ecx,cl
		mov		[BitOffset],ecx
		
		pop		ebx						// back
		mov		al,[K]
		cmp		ebx,[MaxCode]			// exceeded size?
		jb		Make_Into_Code			// no
		cmp		[NBits],12				// less than 12 bit encoding?
		jb		Another_Bit				// give it one more

//---------------------------------------------------------------
// All codes up to 12 bits are used: clear table and restart
//---------------------------------------------------------------

		mov		eax,HASH_CLEAR			// write a clear code.....out of bits
		
		xor		edx,edx					// make sure the DL is CLEAR
		mov		edi,[OutBufferPos]   	// obtain destination address

		cmp		edi,MaxOutBuffer
		jnc		overrun

		mov		ecx,[BitOffset]        	// get bitposition
		jecxz	save2

shift2:
		shl		ax,1
		rcl		edx,1
		loop	shift2
		or		al,[edi]

save2:
		stosw
		mov		al,dl
		stosb
		
		mov		ecx,[NBits]				// get number of bits to advance
		mov		eax,[BitOffset]			// get low word of OutBuffer
		add		eax,ecx
		mov		cl,al
		shr		al,3
		add		[OutBufferPos],eax
		and		cl,7
		movzx	ecx,cl
		mov		[BitOffset],ecx
		
		mov		[NBits],9          		// Starting with 9 bit codes
		mov		[MaxCode],512      		// 10 0000 0000 b
		mov		eax,-1              	// Marked as unused
		mov		ecx,clearSize			// Clear first 256 entries
		mov		edi,offset LZHashBuffer	// Start at the start of buffer
		rep		stosb
		mov		[FreeCode],HASH_FREE 	// Next code to use is first_free

		
		mov		al,[K]                	// Last char
		jmp		Make_Into_Code

//---------------------------------------------------------------
// Extended bit length by a bit, adjusting Max_Code accordingly
//---------------------------------------------------------------

Another_Bit:

		inc		[NBits]               	// one more
		shl		[MaxCode],1           	// Double it's size
		jmp		Make_Into_Code
//----------------------------------------------------------------
// No more input: flush what's left and perform housekeeping
//----------------------------------------------------------------

FoundEOF:

		mov		eax,[PrefixCode]		// write the last code
		
		xor		edx,edx					// make sure the DL is CLEAR
		mov		edi,[OutBufferPos]   	// obtain destination address

		cmp		edi,MaxOutBuffer
		jnc		overrun

		mov		ecx,[BitOffset]        	// get bitposition
		jecxz	save3

shift3:
		shl		ax,1
		rcl		edx,1
		loop	shift3
		or		al,[edi]

save3:
		stosw
		mov		al,dl
		stosb
		
		mov		ecx,[NBits]             // get number of bits to advance
		mov		eax,[BitOffset]         // get low word of OutBuffer
		add		eax,ecx
		mov		cl,al
		shr		al,3
		add		[OutBufferPos],eax
		and		cl,7
		movzx	ecx,cl
		mov		[BitOffset],ecx
		
		mov		eax,HASH_EOF            // write EOF code
		
		xor		edx,edx					// make sure the DL is CLEAR
		mov		edi,[OutBufferPos]   	// obtain destination address

		cmp		edi,MaxOutBuffer
		jnc		overrun

		mov		ecx,[BitOffset]        	// get bitposition
		jecxz	save4
		
shift4:
		shl		ax,1
		rcl		edx,1
		loop	shift4
		or		al,[edi]
	
save4:
		stosw
		mov		al,dl
		stosb
		
		mov		ecx,[NBits]				// get number of bits to advance
		mov		eax,[BitOffset]			// get low word of OutBuffer
		add		eax,ecx
		mov		cl,al
		shr		al,3
		add		[OutBufferPos],eax
		and		cl,7
		movzx	ecx,cl
		mov		[BitOffset],ecx
		
		mov		eax,[BitOffset]			// bits waiting to go?
		or		eax,eax
		je		CompressDone			// no....just close things up and go back
		inc		[OutBufferPos]

CompressDone:

		//	return number of bytes in compressed buffer (add 3 bytes so DWORD loading works in decompress)
		mov		eax,[OutBufferPos]
		add		eax,3
		cmp		eax,MaxOutBuffer
		jnc		overrun
		sub		eax,[OutBuffer]
		jmp		exitcompress
overrun:
		mov		esp,TempSP
		xor		eax,eax
exitcompress:
		mov [result],eax
	}

	return(result);
}


//
//
// LZ Decompression routine, returns length to decompressed output in dest buffer
//
//
DWORD __stdcall gos_LZDecompress( BYTE* dest, BYTE* src, DWORD srcLen )
{
	SPEW(( GROUP_FILES, "gos_LZDecompress(%d bytes)",srcLen ));

	DWORD result = 0;
	
	__asm
	{
		mov		esi,src
		mov		ebx,srcLen

		mov		edi,dest
		xor		eax,eax

		xor		ecx,ecx							// CH and CL used
		lea		ebx,[ebx+esi-3]

		mov		LZOldChain,eax
		mov		LZSrcBufEnd,ebx

		mov		LZChain,eax
		mov		LZMaxIndex,eax

		mov		LZCodeMask,eax
		mov		LZFreeIndex,eax
	
		mov		LZCodeMask,MAX_BIT_INDEX-1
		mov		LZMaxIndex,MAX_BIT_INDEX		//max index for 9 bits == 512

		mov		LZFreeIndex,HASH_FREE			//set index to 258
		mov		LZOldSuffix,al

		mov		ch,BASE_BITS
		jmp		GetCode

//--------------------------------------------------------------------------
//                                                                          
// ClearHash restarts decompression assuming that it is starting from the   
//           beginning                                                      
//                                                                          
//--------------------------------------------------------------------------

ClearHash:
		mov		ch,BASE_BITS		        	//set up for nine bit codes
		mov		LZCodeMask,MAX_BIT_INDEX-1

		mov		LZMaxIndex,MAX_BIT_INDEX    	//max index for 9 bits == 512
		mov		LZFreeIndex,HASH_FREE       	//set index to 258
		
		cmp		esi,LZSrcBufEnd
	   	ja		error

		mov		eax,[esi+0]
		xor		ebx,ebx

		shr		eax,cl

		add		cl,ch
		mov		edx,LZCodeMask

		mov		bl,cl
		and   	cl,07h

		shr   	ebx,3
		and		eax,edx

		add		esi,ebx
		nop

		mov		LZOldChain,eax      	//previous Chain Offset.
		mov		LZOldSuffix,al

		mov		[edi],al
		inc		edi
//-------------------------------------------------------------------------
// ReadCode gets the next hash code (9 BITS) from LZDOSBuff
//         this WILL ReadFile more data if the buffer is empty
//-------------------------------------------------------------------------
GetCode:
		cmp		esi,LZSrcBufEnd
    	ja		error						// Read Passed End?

		mov		eax,[esi+0]
		xor		ebx,ebx

		shr		eax,cl

		add		cl,ch
		mov		edx,LZCodeMask

		mov		bl,cl
		and   	cl,07h

		shr   	ebx,3
		and		eax,edx

		add		esi,ebx
		nop

		cmp		eax,HASH_EOF
		je		eof

		cmp		eax,HASH_CLEAR         		//are we to clear out hash table?
		je		ClearHash
//---------------------------------------------------------------------------
//                                                                           
// Handle Chain acts on two types of Codes, A previously tabled one and a new
// one. On a previously tabled one, the chain value and suffix for that code 
// are preserved into OldSuffix and OldChain. The block operates on searching
// backward in the chains until a chain offset of 0-255 is found (meaning the
// terminal character has been reached.) Each character in the chain is saved
// on the stack.                                                             
//                                                                           
//---------------------------------------------------------------------------

//HandleChain:
		mov		edx,esp 		
		dec		esp

		mov		LZChain,eax        				//Save new chain as well
		lea		ebx, [LZHashBuffer+eax+eax*2]
		
		cmp		eax,LZFreeIndex					//is code in HASH TABLE already?
		jl		InTable      					//if yes, then process chain
	
		mov		al,LZOldSuffix					//get back the old suffix and plant it
		mov		[esp],al                   		//onto the stack for processing later
		dec		esp
		mov		[ebx+2],al				
		mov		eax,LZOldChain     				//get Old chain for creation of Old Chain
		mov		[ebx],ax
		lea		ebx, [LZHashBuffer+eax+eax*2]

InTable:
		test	ah,ah							//(ax<255) is current chain a character?  
		jz		ChainEnd   						//if yes, then begin Print out

		mov		al,[ebx+2]		 				//push suffix onto stack
		mov		[esp],al                   		//onto the stack for processing later

		dec		esp
		movzx	eax,word ptr [ebx]				//get chain to this code
		lea		ebx, [LZHashBuffer+eax+eax*2]
		jmp   InTable            				//and keep filling up
		
ChainEnd:
		mov		LZOldSuffix,al				//save last character in chain
		mov		[esp],al                   		//onto the stack for processing later

		sub		edx,esp 	
		mov		ebx,LZFreeIndex    				//get new code number index

send_bytes:
		mov		al,[esp]
		inc		esp

		mov		[edi],al
		inc		edi

		dec		edx
		jnz		send_bytes

//---------------------------------------------------------------------------
//                                                                           
// Here we add another chain to the hash table so that we continually use it 
// for more decompressions.                                                  
//                                                                           
//---------------------------------------------------------------------------

		mov		al,LZOldSuffix
		mov		edx,LZOldChain

		mov		byte ptr [LZHashBuffer+ebx+ebx*2+2],al
		mov		word ptr [LZHashBuffer+ebx+ebx*2],dx

		inc		ebx
		mov		eax,LZChain

		mov		LZFreeIndex,ebx
		mov		edx,LZMaxIndex

		mov		LZOldChain,eax
		cmp		ebx,edx

		jl    	GetCode

		cmp		ch,12
		je		GetCode

		inc		ch
		mov		ebx,LZCodeMask

		mov		eax,LZMaxIndex
		add		ebx,ebx

		add		eax,eax
		or		ebx,1

		mov		LZMaxIndex,eax
		mov		LZCodeMask,ebx

		jmp   	GetCode

error:
eof:
		sub		edi,dest
		mov		result,edi
	}
	return(result);
}






















































void __stdcall gos_OpenFile( HGOSFILE* file, const char* FileName, gosEnum_FileWriteStatus fwstatus )
{
	char FilterName[MAX_PATH];
	gosASSERT( FileName && *FileName );
//
// Check for \\ characters
//
	if( _mbsstr( (const unsigned char*)FileName+1, (const unsigned char*)"\\\\") )
	{
		const char* Source=FileName;
		char* Dest=FilterName;
		*Dest++=*Source++;

		while( *Source )
		{
			if( *Source=='\\' && *(Source+1)=='\\' )
			{
				Source++;
			}
			*Dest++=*Source++;
		}
		*Dest=0;
		FileName=FilterName;
	}
//
//
//
	#ifdef LAB_ONLY
	sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f %s \"%s\"",CurrentFrameNumber, gos_GetHiResTime(), (fwstatus==READWRITE ? "Create" : fwstatus==READONLY ? "Open" : "Append"), FileName );
	CurrentFileInfo=(CurrentFileInfo+1)&31;
	#endif
	SPEW(( GROUP_FILES, "gos_OpenFile(%s)",FileName ));

	*file = new gosFileStream( FileName, fwstatus );
	NumberFilesOpen++;

	(*file)->pNext=ListOfFiles;
	ListOfFiles=*file;
}


bool InternalDoesFileExist( const char* FileName )
{
	return 0xffffffff!=GetFileAttributes( FileName );
}


bool __stdcall gos_DoesFileExist( const char* FileName )
{
	char FilterName[MAX_PATH];
	gosASSERT( FileName && *FileName );
//
// Check for \\ characters
//
	if( _mbsstr( (const unsigned char*)FileName+1, (const unsigned char*)"\\\\") )
	{
		const char* Source=FileName;
		char* Dest=FilterName;
		*Dest++=*Source++;

		while( *Source )
		{
			if( *Source=='\\' && *(Source+1)=='\\' )
			{
				Source++;
			}
			*Dest++=*Source++;
		}
		*Dest=0;
		FileName=FilterName;
	}
//
// Debugging info
//
	#ifdef LAB_ONLY
	char FileBuffer[128];
	if( strlen(FileName)<47 )
	{
		strcpy( FileBuffer, FileName );
	}
	else
	{
		strcpy( FileBuffer, "..." );
		strcat( FileBuffer, &FileName[strlen(FileName)-43] );
	}
	sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f DoesFileExist \"%s\"",CurrentFrameNumber, gos_GetHiResTime(), FileBuffer );
	CurrentFileInfo=(CurrentFileInfo+1)&31;
	#endif
	SPEW(( GROUP_FILES, "gos_DoesFileExist(%s)",FileName ));
//
// Check for being hooked
//
	if(!RecursiveFileFunction && Environment.HookDoesFileExist)
	{
		RecursiveFileFunction = 1;
		bool fRet = Environment.HookDoesFileExist(FileName);
		RecursiveFileFunction = 0;
		return fRet;
	}
//
//
//
	return 0xffffffff!=GetFileAttributes( FileName );
}



void __stdcall gos_CloseFile(HGOSFILE file)
{
	#ifdef LAB_ONLY
	sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f CloseFile \"%s\"",CurrentFrameNumber, gos_GetHiResTime(), file->m_Filename );
	CurrentFileInfo=(CurrentFileInfo+1)&31;
	#endif
	SPEW(( GROUP_FILES, "gos_CloseFile(%s)",file->m_Filename ));
//
// Remove from chain
//
	NumberFilesOpen--;
	if( ListOfFiles==file )
	{
		ListOfFiles=file->pNext;
		delete file;
		file = NULL;
	}
	else
	{
		gosFileStream* pLast=0;
		gosFileStream* pTemp=ListOfFiles;
		while( pTemp )
		{
			pLast=pTemp;
			pTemp=pTemp->pNext;
			if( pTemp==file )
			{
				pLast->pNext=file->pNext;
				delete file;
				file = NULL;
				return;
			}
		}
	}
}

DWORD __stdcall gos_ReadFile( HGOSFILE file, void* buf, DWORD size )
{
	SPEW(( GROUP_FILES, "gos_ReadFile(%s, %d bytes)",file->m_Filename,size ));

	DWORD sizeRead = file->Read(buf,size);

	#ifdef LAB_ONLY
	file->BytesTransfered+=sizeRead;
	char FileBuffer[128];
	if( strlen(file->m_Filename)<47 )
	{
		strcpy( FileBuffer, file->m_Filename );
	}
	else
	{
		strcpy( FileBuffer, "..." );
		strcat( FileBuffer, &file->m_Filename[strlen(file->m_Filename)-43] );
	}
	if( sizeRead<1024 )
		sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f ReadFile \"%s\" %db",CurrentFrameNumber, gos_GetHiResTime(), FileBuffer, sizeRead );
	else
		sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f ReadFile \"%s\" %dk",CurrentFrameNumber, gos_GetHiResTime(), FileBuffer, sizeRead/1024 );
	CurrentFileInfo=(CurrentFileInfo+1)&31;
	#endif

	return sizeRead;
}

DWORD __stdcall gos_WriteFile( HGOSFILE file, const void* buf, DWORD size )
{
	#ifdef LAB_ONLY
	sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f ReadFile \"%s\" %dk",CurrentFrameNumber, gos_GetHiResTime(), file->m_Filename, size/1024 );
	CurrentFileInfo=(CurrentFileInfo+1)&31;
	SPEW(( GROUP_FILES, "gos_WriteFile(%s, %d bytes)",file->m_Filename,size ));
	file->BytesTransfered+=size;
	#endif

	return file->Write(buf,size);
}

DWORD __stdcall gos_SeekFile( HGOSFILE file, gosEnum_FileSeekType type, int offset )
{
	#ifdef LAB_ONLY
	char FileBuffer[128];
	
	if( strlen(file->m_Filename)<47 )
	{
		strcpy( FileBuffer, file->m_Filename );
	}
	else
	{
		strcpy( FileBuffer, "..." );
		strcat( FileBuffer, &file->m_Filename[strlen(file->m_Filename)-43] );
	}
	sprintf( &FileInfo[CurrentFileInfo][0], "% 7d% 10.3f Seek %s %d \"%s\"",CurrentFrameNumber, gos_GetHiResTime(), (type==FROMSTART ? "to" : type==FROMCURRENT ? "from current" : "from end"), offset, FileBuffer );
	CurrentFileInfo=(CurrentFileInfo+1)&31;
	SPEW(( GROUP_FILES, "gos_SeekFile(%s)",file->m_Filename ));
	#endif

	return file->Seek(offset,type);
}



gosFileStream::gosFileStream( const char *FileName, gosEnum_FileWriteStatus fwstatus )
{
	gosASSERT( FileName );
	
	m_writeEnabled = fwstatus;
	strcpy( m_Filename, FileName );

	BytesTransfered=0;

	pNext=0;
	m_hFile = 0;

	do
	{
		if( m_writeEnabled==READONLY )
		{
			m_hFile = CreateFile( FileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL );
			if( m_hFile==INVALID_HANDLE_VALUE )
			{
				char Buffer[256];
				sprintf( Buffer, gos_GetResourceString(gLanguageDLL, GOS_FILE_NOT_FOUND), FileName );
				if( ErrorHandler( gos_ErrorStop+gos_ErrorRetry+gos_ErrorNoRegisters+gos_ErrorPop2, Buffer ) )
					_asm int 3;
			}
		}
		else if( m_writeEnabled==READWRITE )
		{
			m_hFile = CreateFile( FileName, GENERIC_WRITE|GENERIC_READ, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
			if( m_hFile==INVALID_HANDLE_VALUE )
			{
				char Buffer[256];
				sprintf( Buffer, gos_GetResourceString(gLanguageDLL, GOS_CANT_CREATE_FILE), FileName );
				if( ErrorHandler( gos_ErrorStop+gos_ErrorRetry+gos_ErrorNoRegisters+gos_ErrorPop2, Buffer ) )
					_asm int 3;
			}
		}
		else if( m_writeEnabled==APPEND )
		{
			m_hFile = CreateFile( FileName, GENERIC_WRITE|GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
			if( m_hFile==INVALID_HANDLE_VALUE )
			{
				char Buffer[256];
				sprintf( Buffer, gos_GetResourceString(gLanguageDLL, GOS_CANT_CREATE_OR_OPEN_FILE), FileName );
				if( ErrorHandler( gos_ErrorStop+gos_ErrorRetry+gos_ErrorNoRegisters+gos_ErrorPop2, Buffer ) )
					_asm int 3;
			}
		}
		else
			STOP(( "Invalid file mode" ));
	} while ( m_hFile == INVALID_HANDLE_VALUE );
}

gosFileStream::~gosFileStream()
{
    CloseHandle( m_hFile );
}

DWORD gosFileStream::Seek( int where, gosEnum_FileSeekType from_where )
{
	DWORD result=-1;

	do
	{
		switch( from_where )
		{
			case FROMSTART:
				result=SetFilePointer( m_hFile, where, 0, FILE_BEGIN );
				break;
			case FROMEND:
				result=SetFilePointer( m_hFile, where, 0, FILE_END );
				break;
			default:
				result=SetFilePointer( m_hFile, where, 0, FILE_CURRENT );
				break;
		}
	
		if( -1==result )
		{
			char Buffer[256];
			sprintf( Buffer, gos_GetResourceString(gLanguageDLL, GOS_PROBLEM_READING_FILE), m_Filename );
 			if( ErrorHandler( gos_ErrorStop+gos_ErrorRetry+gos_ErrorNoRegisters+gos_ErrorPop2, Buffer ) )
				_asm int 3;
		}

	} while( -1==result );

	return result;
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
DWORD gosFileStream::Read( void* buffer, DWORD Length )
{
	DWORD Size;
	DWORD result=0;

	do
	{
		result=ReadFile( m_hFile, buffer, Length, &Size, NULL );  

		if( 0==result )
		{
			char Buffer[256];
			sprintf( Buffer, gos_GetResourceString(gLanguageDLL, GOS_PROBLEM_READING_FILE), m_Filename );
			if( ErrorHandler( gos_ErrorStop+gos_ErrorRetry+gos_ErrorNoRegisters+gos_ErrorPop2, Buffer ) )
				_asm int 3;
		}

	} while( 0==result );

	#ifdef LAB_ONLY
	BytesLoaded+=Size;
	#endif
	return Size;
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
DWORD gosFileStream::Write( const void* buffer, DWORD Length )
{
	if( m_writeEnabled!=READWRITE && m_writeEnabled!=APPEND )
		STOP(( "File '%s' is read-only", m_Filename ));

	DWORD Size;
	DWORD result=0;

	do
	{
		result=WriteFile( m_hFile, buffer, Length, &Size, NULL );  

		if( 0==result )
		{
			char Buffer[256];
			sprintf( Buffer, "There was a problem writing file %s", m_Filename );
			if( ErrorHandler( gos_ErrorStop+gos_ErrorRetry+gos_ErrorNoRegisters+gos_ErrorPop2, Buffer ) )
				_asm int 3;
		}

	} while( 0==result );

	return Size;
}

