//
// streaming/cacheloader.cpp
//
// Copyright (C) 1999-2009 Rockstar Games.  All Rights Reserved.
//

#include "streaming/cacheloader.h"

#include "file/asset.h"
#include "file/default_paths.h"
#include "net/netHardware.h"  // For netHardware::GetLocalIpAddress
#include "net/netAddress.h"
#include "system/param.h"
#include "system/platform.h"

#include "streaming/packfilemanager.h"
#include "streaming/streaming_channel.h"
#include "streaming/streamingengine.h"

#include "fwscene/mapdata/mapdata.h"

#include "data/datprotect.h"
#include "diag/errorcodes.h"

#include <time.h>

#if !__FINAL
#include "system/exec.h"
#endif

#if RSG_PC
#include <windef.h>
#include <winbase.h>
#endif

STREAMING_OPTIMISATIONS()

#if !__FINAL
namespace rage {
	XPARAM(update);
	XPARAM(noupdate);
}
	XPARAM(buildDLCCacheData);
#endif

namespace rage {

#define CACHELOADER_BUILD_CACHE					(!__FINAL)					// if defined then a cache file can be built and saved.						
#define CACHELOADER_VERSION_NUMBER				46							// increment this if you want to force a new cache file
#define	CACHELOADER_STANDARD_CACHE_BUFFER_SIZE	(1500*1024)					// standard size of the cache buffer, this memory is freed once used so it's size can be overly generous if required.
#define CACHELOADER_DLC_CACHE_BUFFER_SIZE (256 * 1024)
#define CACHELOADER_STANDARD_CACHE_FILENAME		"platform:/data/cache.dat"  // In a single level system only one cache file is used and is of this filename.

#define CACHELOADER_VERSION_TOKEN				"[VERSION]"					// a token that indicate the version number follows in the file.
#define CACHELOADER_INVALID_TOKEN				"!CACHE_INVALID!"			// a string added to a cache file to indicate its out of date
#define CACHELOADER_MODULE_START_TOKEN			"<module>"					// token used in parsing of cache file
#define CACHELOADER_MODULE_END_TOKEN			"</module>"					// token used in parsing of cache file
#define CACHELOADER_FILEDATE_START_TOKEN		"<fileDates>"				// token used in parsing of cache file
#define CACHELOADER_FILEDATE_END_TOKEN			"</fileDates>"				// token used in parsing of cache file
#define CACHELOADER_ENCRYPTION_TOKEN			"[ENCRYPTED]"				// token that indicates the the following cache file is encrypted

#define CACHELOADER_USES_ENCRYPTION				(0)							// Defines if the cache file uses encryption
#define CACHELOADER_HEADER_SIZE					(100)						// Size of the header of the cache file -> data won't start until after this. Includes room for signature

#define CACHELOADER_DISABLE_COMMAND_LINE			disablecacheloader
PARAM(disablecacheloader,							"Disable cached bounds files");
#define PARAM_CACHELOADER_DISABLE_COMMAND_LINE		PARAM_disablecacheloader

#define CACHELOADER_SHAREDATA_COMMAND_LINE			sharecachedata
PARAM(sharecachedata,								"Cache data is being served by a single machine to multiple 'clients'");
#define PARAM_CACHELOADER_SHAREDATA_COMMAND_LINE	PARAM_sharecachedata 

#define CACHELOADER_DISABLE_ENCRPYTION_COMMAND_LINE nocacheloaderencryption
PARAM(nocacheloaderencryption,								"Cache data is encrypted");
#define PARAM_CACHELOADER_DISABLE_ENCRPYTION_COMMAND_LINE PARAM_nocacheloaderencryption

#define CACHELOADER_FORCE_ENCRPYTION_COMMAND_LINE forcecacheloaderencryption
PARAM(forcecacheloaderencryption,								"Cache data is encrypted");
#define PARAM_CACHELOADER_FORCE_ENCRPYTION_COMMAND_LINE PARAM_forcecacheloaderencryption

#if CACHELOADER_BUILD_CACHE
PARAM(exitaftercachesave, "Cause the game to terminate immediately after the cache file has been generated");
#endif

NOSTRIP_PARAM(nocachetimestamps,					"Skip the timestmap check for cache validation");

#if __BANK
PARAM(saveLoadedCacheDataToTextFile, "Save the loaded cache data to a text file so that it's easier to read and filecompare. Pass a string containing the absolute path to a folder where the text file should be saved to");
#endif // __BANK

RAGE_DEFINE_SUBCHANNEL(streaming,cacheloader)

#define cacheloaderAssert(cond)							RAGE_ASSERT(streaming_cacheloader,cond)
#define cacheloaderAssertf(cond,fmt,...)				RAGE_ASSERTF(streaming_cacheloader,cond,fmt,##__VA_ARGS__)
#define cacheloaderVerify(cond)							RAGE_VERIFY(streaming_cacheloader,cond)
#define cacheloaderVerifyf(cond,fmt,...)				RAGE_VERIFYF(streaming_cacheloader,cond,fmt,##__VA_ARGS__)
#define cacheloaderErrorf(fmt,...)						RAGE_ERRORF(streaming_cacheloader,fmt,##__VA_ARGS__)
#define cacheloaderWarningf(fmt,...)					RAGE_WARNINGF(streaming_cacheloader,fmt,##__VA_ARGS__)
#define cacheloaderDisplayf(fmt,...)					RAGE_DISPLAYF(streaming_cacheloader,fmt,##__VA_ARGS__)
#define cacheloaderDebugf1(fmt,...)						RAGE_DEBUGF1(streaming_cacheloader,fmt,##__VA_ARGS__)

strCacheLoader::CModuleDescArray strCacheLoader::ms_modules;		// a collection of modules for which we have data about.	

s32 strCacheLoader::ms_cacheBufferSize = CACHELOADER_STANDARD_CACHE_BUFFER_SIZE;
fiStream*	strCacheLoader::ms_cache_stream		= NULL;				// handle to the cache file
bool		strCacheLoader::ms_bCacheEnabled	= true;				// system enable/disable
bool		strCacheLoader::ms_bCacheReadonly = false;			// status of cache file
bool		strCacheLoader::ms_bLoadCache		= true;				// wish to load a cache file
bool		strCacheLoader::ms_bSaveCache		= true;				// wish to save a cache file
bool		strCacheLoader::ms_bPackagedBuild	= false;			// running a packaged build, has its own cache file
bool		strCacheLoader::ms_bCacheUsesEncryption = CACHELOADER_USES_ENCRYPTION;	// cache file is encrypted using datprotect
bool strCacheLoader::ms_bFilesChanged = false;

char*		strCacheLoader::ms_WorkBuffer		= NULL;				// cache file memory buffer
char*		strCacheLoader::ms_WorkBufferPos	= NULL;				// cache file position in the buffer
char*		strCacheLoader::ms_pFileDatesStart	= NULL;				// cache file dates begin
char*		strCacheLoader::ms_pFileDatesEnd	= NULL;				// cache file dates end

char		strCacheLoader::ms_cCacheFilename[RAGE_MAX_PATH];		// the computed filename of the cache file

char strCacheLoader::ms_cDeviceAndPath[RAGE_MAX_PATH] = { 0 };
strCacheLoaderMode strCacheLoader::ms_mode = SCL_DEFAULT;
atMap<s32, bool> strCacheLoader::ms_dlcArchives;
atArray<u32> strCacheLoader::ms_readCaches;

/////////////////////////////////////////////////////////////////////////////////////
template<int _A>u32 Align(u32 v)
{
	return (v+_A-1)&(~(_A-1));
}

bool strCacheLoader::GetReadyOnly()
{
	return ms_bCacheReadonly;
}

void strCacheLoader::SetReadOnly(bool value)
{
	cacheloaderDisplayf("strCacheLoader::SetReadOnly = %i", (s32)value);
	ms_bCacheReadonly = value;
}

/////////////////////////////////////////////////////////////////////////////////////
void strCacheLoader::Init(const char *pFilename, const char* pDeviceAndPath, strCacheLoaderMode initMode, const atMap<s32, bool>& dlcArchives)
{
	if(PARAM_CACHELOADER_DISABLE_COMMAND_LINE.Get())
		ms_bCacheEnabled = false;

	if(PARAM_CACHELOADER_FORCE_ENCRPYTION_COMMAND_LINE.Get())
		ms_bCacheUsesEncryption = true;

	if(PARAM_CACHELOADER_DISABLE_ENCRPYTION_COMMAND_LINE.Get())
		ms_bCacheUsesEncryption = false;

	
	ms_bLoadCache = false;
	ms_bSaveCache = false;
	ms_pFileDatesStart = NULL;
	ms_pFileDatesEnd = NULL;
	ms_mode = initMode;
	ms_dlcArchives = dlcArchives;

	if (ms_mode == SCL_DLC)
		ms_cacheBufferSize = CACHELOADER_DLC_CACHE_BUFFER_SIZE;
	else
		ms_cacheBufferSize = CACHELOADER_STANDARD_CACHE_BUFFER_SIZE;

	if (pDeviceAndPath != NULL)
		formatf(ms_cDeviceAndPath, sizeof(ms_cDeviceAndPath), "%s", pDeviceAndPath);
	else
		memset(ms_cDeviceAndPath, 0, sizeof(ms_cDeviceAndPath));

	BuildCacheFilename(pFilename);	

	if (!ms_bCacheEnabled)
	{
		cacheloaderDisplayf("Cache IS NOT IN OPERATION");
		return;
	}

	// Check to see the file status of the cache file if it is read only we can't try to write it.
	DetermineCacheFileReadOnly();	

	// before we do anything, we need to see whether the current cache file is in a good state to use:
	atHashString cacheFilenameHash = atHashString(ms_cCacheFilename);
	bool bCacheIsValid = CheckCacheFileIsValid(pFilename);
	bool bReadCache = ms_readCaches.Find(cacheFilenameHash) != -1;

	ms_bLoadCache = bCacheIsValid && !bReadCache;
	ms_bSaveCache = !bCacheIsValid;

	// Make sure we read this cache file only once
	if (ms_bLoadCache && !bReadCache)
		ms_readCaches.PushAndGrow(cacheFilenameHash);

	// Temp memory allocated to load cache file in.
	AllocateWorkBuffer();

	// load into allocated temporary buffer
	if (ms_bLoadCache)
	{
		LoadCache();
		DecryptWorkBuffer();
		CloseCacheFile();
	}
}

/////////////////////////////////////////////////////////////////////////////////////
s32 strCacheLoader::ParseCache()
{
	// store the buffer start and end...
	char *pSavedBufferStart = ms_WorkBuffer;
	char *pPossibleFooterPos = NULL;
	s32 numModulesFound = 0;

	// go thru the memory and sort out where each module starts:
	while (pSavedBufferStart < ms_WorkBufferPos)
	{
		char line[1024];

		pPossibleFooterPos = pSavedBufferStart;  // store the start of the line, so we can use it as the footer later

		ReadLine(pSavedBufferStart, line);

		if (!strcmp(CACHELOADER_MODULE_END_TOKEN, line))
		{
			cacheloaderDisplayf("%s Located", CACHELOADER_MODULE_END_TOKEN);
			// set the footer for the last module in use as we have found the end of a module:
			if (numModulesFound > 0 && pPossibleFooterPos)
			{
				// set as the footer:
				GetModules().m_modules[numModulesFound-1].m_footer_pos = pPossibleFooterPos;
			}
		}
		else if (!strcmp(CACHELOADER_MODULE_START_TOKEN, line))
		{
			cacheloaderDisplayf("%s Located", CACHELOADER_MODULE_START_TOKEN);

			char moduleName[32];			

			ReadLine(pSavedBufferStart, line);

			// get module name and section size
			sscanf(line, "%s", moduleName);
			cacheloaderDisplayf("Module Name %s Located", moduleName);

			s32 moduleSize = 0;
			memcpy(&moduleSize,pSavedBufferStart,4);
			cacheloaderDisplayf("Module Size %d read at %p", moduleSize, pSavedBufferStart);

			pSavedBufferStart += 4;

			for (int c = 0;  c < GetModules().m_numModules; c++)
			{
				if (!strcmp(GetModules().m_modules[c].m_moduleName, moduleName))
				{
					GetModules().m_modules[c].m_header_pos = pSavedBufferStart;
					break; // break out of for loop
				}
			}

			pSavedBufferStart += moduleSize;

			cacheloaderDisplayf("Module %s Located", moduleName);
			numModulesFound++;  // move onto setting next module
		}
		else if(!strcmp(CACHELOADER_FILEDATE_END_TOKEN, line))
		{
			cacheloaderDisplayf("%s Located", CACHELOADER_FILEDATE_END_TOKEN);
			cacheloaderAssert(ms_pFileDatesStart);
			ms_pFileDatesEnd = pPossibleFooterPos;
		}
		else if(!strcmp(CACHELOADER_FILEDATE_START_TOKEN, line))
		{
			cacheloaderDisplayf("%s Located", CACHELOADER_FILEDATE_START_TOKEN);
			cacheloaderAssert(ms_pFileDatesStart == NULL);
			cacheloaderAssert(ms_pFileDatesEnd == NULL);
			ms_pFileDatesStart = pSavedBufferStart;
		}
	}

	return numModulesFound;
}

/////////////////////////////////////////////////////////////////////////////////////
// Although this function is called load it actually has already loaded the cache file.
// The cache file in work memory is parsed here.
bool strCacheLoader::Load()
{
	if(ms_bLoadCache == false || ms_bCacheEnabled == false)
		return false;

	cacheloaderDisplayf("Load");

	s32 numModulesFound = ParseCache();

	// the number of modules found didn't match the number of modules expected:
	if (numModulesFound != GetModules().GetNumModules())
	{
		cacheloaderDisplayf("the number of modules found didnt match the number of modules expected");

		SetCacheAsInvalid();  // set the cache file as invalid
		ms_bLoadCache = false;
		ms_bSaveCache = true;
		return false;
	}
	
	// If cache is writable check if there are files that are newer than the cache file
	ms_bFilesChanged = !CheckCacheFileDates();

	// If in DLC mode and files changed, re-gen all cache files
	if (ms_bFilesChanged && ms_mode == SCL_DLC) 
	{
		cacheloaderDisplayf("DLC files have changed");

		SetCacheAsInvalid();  // set the cache file as invalid
		ms_bLoadCache = false;
		ms_bSaveCache = true;
		return false;
	}

	// The file dates are NOT the same so we want to update the cache file
	ms_bSaveCache = !ms_bCacheReadonly && ms_bFilesChanged;

	if (ms_bFilesChanged)
	{
		cacheloaderDisplayf("file dates differ");

		if (ms_bCacheReadonly)
		{
			cacheloaderDisplayf("the cache file is read only.");
		}
	}	

	{
		cacheloaderDisplayf("the modules are now to be loaded");

		fiStream *pTextFileToWriteTo = NULL;
#if __BANK
		const char *pPathToSaveTextFilesTo = NULL;
		if (PARAM_saveLoadedCacheDataToTextFile.Get(pPathToSaveTextFilesTo))
		{
			if (cacheloaderVerifyf(pPathToSaveTextFilesTo, "-saveLoadedCacheDataToTextFile command line param doesn't specify a string to use as the path to save to"))
			{
				if (cacheloaderVerifyf(strlen(pPathToSaveTextFilesTo) >= 3, "-saveLoadedCacheDataToTextFile command line param string is less than 3 characters long"))
				{
					char filenameOfTextFile[RAGE_MAX_PATH];
					strcpy(filenameOfTextFile, ASSET.FileName(GetCacheFilename()));
					ASSET.RemoveExtensionFromPath(filenameOfTextFile, RAGE_MAX_PATH, filenameOfTextFile);
					safecat(filenameOfTextFile, ".txt");

					char fullPathToTextFile[RAGE_MAX_PATH];
					strcpy(fullPathToTextFile, pPathToSaveTextFilesTo);
					if ((fullPathToTextFile[strlen(fullPathToTextFile)-1] != '\\') 
						&& (fullPathToTextFile[strlen(fullPathToTextFile)-1] != '/'))
					{
						strcat(fullPathToTextFile, "\\");
					}
					strcat(fullPathToTextFile, filenameOfTextFile);

					ASSET.CreateLeadingPath(fullPathToTextFile);
					pTextFileToWriteTo = ASSET.Create(fullPathToTextFile, "");
				}
			}
		}
#endif // __BANK

		for(s32 i=0; i<GetModules().GetNumModules(); i++)
		{
			strStreamingEngine::GetLoader().CallKeepAliveCallbackIfNecessary();
			cacheloaderDisplayf("Cacheloader : Loading Module #%d/%d %s", i+1,GetModules().GetNumModules(), GetModules().m_modules[i].m_moduleName);

			if(!GetModules().Load(i, pTextFileToWriteTo))
			{			
				return false;
			}
		}

		if (pTextFileToWriteTo)
		{
			pTextFileToWriteTo->Close();
			pTextFileToWriteTo = NULL;
		}
	}

	cacheloaderDisplayf("the modules loaded ok!");

	return true;
}

/////////////////////////////////////////////////////////////////////////////////////
void strCacheLoader::Shutdown()
{
	if (!ms_bCacheEnabled)
		return;

#if CACHELOADER_BUILD_CACHE
	if (ms_bSaveCache)
	{
		// now we can actually save all the data back to the cache file:

		// check we can write to the device the cachefile is on
		if(!ms_bCacheReadonly)
		{
			cacheloaderDisplayf("Writing Cache");

			ms_cache_stream = ASSET.Create(GetCacheFilename(), ""); // we are going to overwrite the current cache file
			cacheloaderAssertf((ms_cache_stream!=NULL), "strCacheLoader::Shutdown - Cannot open Cache file '%s' for writing! - consider either using -sharecachedata ( if you share data from one machine) or -disablecacheloader (if you want to disable this optional accelerator) ", GetCacheFilename());

			if (!ms_cache_stream)
			{
				fiStream *pHandle = ASSET.Open(GetCacheFilename(),""); 
				cacheloaderAssertf(pHandle==NULL, "strCacheLoader::Shutdown - Couldn't open Cache file '%s' for writing BUT I can for reading thus there must be a permissions problem.!- consider either using -sharecachedata ( if you share data from one machine) or -disablecacheloader (if you want to disable this optional accelerator) ", GetCacheFilename());
				if (pHandle)
				{
					pHandle->Close();
				}
			}

			WriteCacheLoaderHeader();  // write the version number to the cache file

			EncryptWorkBuffer();  // encrypt the work buffer and write out the signature to the file

			WriteWorkBuffer(); // write the work buffer to the file

			// close the cache file:
			CloseCacheFile();
			ms_cache_stream = NULL;
		}
	}

#if RSG_PC
	if(PARAM_exitaftercachesave.Get())
	{
		cacheloaderDisplayf("Completed processing cachefile - terminating per -exitaftercachesave option.");
		// maybe want to do some cleanup or flush buffers before exiting?
		ExitProcess(0);
	}
#endif	// RSG_PC

#endif // CACHELOADER_BUILD_CACHE

	FreeWorkBuffer();
	ms_dlcArchives.Reset();
	ms_mode = SCL_DEFAULT;
	ms_bFilesChanged = false;
}

/////////////////////////////////////////////////////////////////////////////////////
// Horrible function that needs tidied, I think it reads the buffer in pSavedBufferStart into pLine
void strCacheLoader::ReadLine(char*& pSavedBufferStart, char* pLine)
{
	//char *pBase = pLine;
	while (*pSavedBufferStart != '\n' && *pSavedBufferStart != '\0')
	{
		*pLine = *pSavedBufferStart;
		pSavedBufferStart++;
		pLine++;
	}
	*pLine = '\0';
	//cacheloaderDisplayf("CacheLoader Line: %s (%x)", pBase,pSavedBufferStart);
	pSavedBufferStart++;  // move onto start of next line
}

/////////////////////////////////////////////////////////////////////////////////////
void strCacheLoader::Save()
{
	// Make sure we read this cache file only once
	if (ms_bSaveCache)
	{
		atHashString cacheFilenameHash = atHashString(ms_cCacheFilename);

		if (ms_readCaches.Find(cacheFilenameHash) == -1)
			ms_readCaches.PushAndGrow(cacheFilenameHash);
	}

#if CACHELOADER_BUILD_CACHE
	if((!ms_bSaveCache) || (!ms_bCacheEnabled))
		return;

	cacheloaderDisplayf("Saving cache file");

	ms_WorkBufferPos = ms_WorkBuffer;

	char fullPathToTextFile[RAGE_MAX_PATH];
	strcpy(fullPathToTextFile, GetCacheFilename());
	ASSET.RemoveExtensionFromPath(fullPathToTextFile, RAGE_MAX_PATH, fullPathToTextFile);
	safecat(fullPathToTextFile, ".txt");

	ASSET.CreateLeadingPath(fullPathToTextFile);
	fiStream* pTextFileToWriteTo = ASSET.Create(fullPathToTextFile, "");

	WriteCacheFileDates();
	for(s32 i=0; i<GetModules().GetNumModules(); i++)
	{
		cacheloaderDisplayf("Cacheloader : Saving Module #%d %s", i, GetModules().m_modules[i].m_moduleName);
		GetModules().Save(i, pTextFileToWriteTo);
	}

	if (pTextFileToWriteTo)
	{
		pTextFileToWriteTo->Close();
		pTextFileToWriteTo = NULL;
	}

#endif // CACHELOADER_BUILD_CACHE
}

/////////////////////////////////////////////////////////////////////////////////////
// NAME:	strCacheLoader::SetCacheAsInvalid
// PURPOSE:	sets and flags the cache file as invalid so next time we run the game
//			we check for this and know whether to use it or not or attempt to create
//			a new cache file based on new data
// RETURNS: NONE
/////////////////////////////////////////////////////////////////////////////////////
void strCacheLoader::SetCacheAsInvalid()
{
#if CACHELOADER_BUILD_CACHE
	// from this point the cache file will not be used
	CloseCacheFile();  // close the cache file if it is currently open (probably not)

	//CFileMgr::SetDir("");
	ms_cache_stream = ASSET.Create(GetCacheFilename(),"");  // open for writing

	cacheloaderAssertf(ms_cache_stream!=NULL, "strCacheLoader::SetCacheAsInvalid - Cannot open Cache file '%s' for writing! - consider either using -sharecachedata ( if you share data from one machine) or -disablecacheloader (if you want to disable this optional accelerator) ", GetCacheFilename());

	if(!ms_cache_stream)
		return;

	char str[255];
	sprintf(str, "%s\n",CACHELOADER_INVALID_TOKEN);
	ms_cache_stream->Write(str, istrlen(str));

	CloseCacheFile();
#endif // CACHELOADER_BUILD_CACHE
}


/////////////////////////////////////////////////////////////////////////////////////
// NAME:	strCacheLoader::CheckCacheFileIsValid
// PURPOSE:	checks whether the cache file is valid and fills out the load and save
//			flags based on the results
/////////////////////////////////////////////////////////////////////////////////////
bool strCacheLoader::CheckCacheFileIsValid(const char* UNUSED_PARAM(pFilename))
{
	cacheloaderAssert(ms_bCacheEnabled);

	//CFileMgr::SetDir("");
	ms_cache_stream = ASSET.Open(GetCacheFilename(),"");

	if (ms_cache_stream==NULL)
	{
		cacheloaderDisplayf("Cachefile : FileHandle invalid, and thus is invalid.");
		return false;
	}

	if (!CheckVersionNumber())  // if a version number could not be found
	{
		cacheloaderDisplayf("Cachefile : no version number found, thus is invalid.");
		CloseCacheFile();  // close it before exiting the function
		return false;
	}


// 	const fiDevice* pCacheDevice = fiDevice::GetDevice(GetCacheFilename());
// 	u64 cachefiletime = pCacheDevice?pCacheDevice->GetFileTime(GetCacheFilename()):0xFFFFFFFFFFFFFFFFu;
// 	const fiDevice* pDatDevice = fiDevice::GetDevice(pFilename);
// 	u64 datfiletime = pDatDevice?pDatDevice->GetFileTime(pFilename):0x0000000000000000u;
// 
// 	if (datfiletime > cachefiletime)
// 	{
// 		cacheloaderDisplayf("Cachefile : older than %s, thus is invalid.", pFilename);
// 		CloseCacheFile(); 
// 		return false;
// 	}

	// now check for invalid:
	ms_cache_stream->Seek(0);   // seek to start
	char line[256] = "";
	fgetline(line,sizeof(line),ms_cache_stream);
	CloseCacheFile();

	if (!strcmp(line, CACHELOADER_INVALID_TOKEN))
	{
		cacheloaderDisplayf("%s Cache will not be loaded.", CACHELOADER_INVALID_TOKEN);
		return false;
	}

	return true;
}

/////////////////////////////////////////////////////////////////////////////////////
// NAME:	strCacheLoader::CloseCacheFile
// PURPOSE:	closes the cache file if its open
// RETURNS: NONE
/////////////////////////////////////////////////////////////////////////////////////
void strCacheLoader::CloseCacheFile()
{
	if (ms_cache_stream)
	{
		ms_cache_stream->Close();
		ms_cache_stream = NULL;
		//CFileMgr::SetDir("");
	}
}

/////////////////////////////////////////////////////////////////////////////////////
// NAME:	strCacheLoader::CModuleDescArray::AddModule
// PURPOSE:	adds a module to the cache loader
// RETURNS: module id
/////////////////////////////////////////////////////////////////////////////////////
s32 strCacheLoader::CModuleDescArray::AddModule(const char* pName,
											  CModuleDesc::LoadFn loadFn,
											  CModuleDesc::SaveFn saveFn,
											  s32 entrySize)
{
	cacheloaderAssertf(m_numModules < CACHELOADER_MAX_CACHE_MODULES, "Too many modules using Cache Loader");

	strcpy(&m_modules[m_numModules].m_moduleName[0], pName);
	m_modules[m_numModules].m_loadFn = loadFn;
	m_modules[m_numModules].m_saveFn = saveFn;
	m_modules[m_numModules].m_entrySize = entrySize;

	m_numModules++;

	return m_numModules-1;
}

/////////////////////////////////////////////////////////////////////////////////////
// NAME:	strCacheLoader::WriteVersionNumber
// PURPOSE:	writes the current version number to the file
// RETURNS: NONE
/////////////////////////////////////////////////////////////////////////////////////
void strCacheLoader::WriteCacheLoaderHeader()
{
	if(!ms_cache_stream)
		return;
	char str[255];
	sprintf(str, "%s\n%d\n",CACHELOADER_VERSION_TOKEN,CACHELOADER_VERSION_NUMBER);
	
	ms_cache_stream->Write(str,istrlen(str)); // write actual header

	if(ms_bCacheUsesEncryption)
	{
		sprintf(str, "%s\n", CACHELOADER_ENCRYPTION_TOKEN);
		ms_cache_stream->Write(str, istrlen(str)); // write encryption status
	}
}

/////////////////////////////////////////////////////////////////////////////////////
// NAME:	strCacheLoader::CheckVersionNumber
// PURPOSE:	checks the version number of the cache file with the one in CacheLoader.h
// RETURNS: TRUE if found or FALSE if not found
/////////////////////////////////////////////////////////////////////////////////////
bool strCacheLoader::CheckVersionNumber()
{
	if(!ms_cache_stream)
		return false;

	ms_cache_stream->Seek(0);  // seek to start to find the correct header

	char line[512] = "";

	while( fgetline(line,sizeof(line),ms_cache_stream) )
	{
		if(line[0] == ';' || line[0] == '\0' || line[0] == '*')  // skip any comment lines
			continue;

		if (!strcmp(line, CACHELOADER_VERSION_TOKEN))  // found header label
		{
			line[0]='\0';
			fgetline(line,sizeof(line),ms_cache_stream);

			s32 version_number_found = atoi(line);

			cacheloaderDisplayf("Cacheloader : Version %d found", version_number_found);
			if (version_number_found == CACHELOADER_VERSION_NUMBER)
			{

				// Check for encryption
				line[0]='\0';
				fgetline(line,sizeof(line),ms_cache_stream);
				if(!strcmp(line, CACHELOADER_ENCRYPTION_TOKEN))
				{			
					cacheloaderDisplayf("Cacheloader : Encrption Token %s", line);
					if(!ms_bCacheUsesEncryption)
					{
						cacheloaderErrorf("Encrypted Cacheloader File when encryption is disabled - cacheloader file will be recreated.");
						return false; // Encrypted cachefile when we're not using encryption
					}
				}
				else
				{
					if(ms_bCacheUsesEncryption)
					{
						cacheloaderErrorf("Unencrypted Cacheloader File when encryption is enabled - cacheloader file will be recreated.");
						return false; // Encrypted cachefile when we're not using encryption
					}
				}

				return true;  // break out of while loop as we have found the header
			}
			else
			{
				return false; // didnt find the correct version number
			}
		}
	}

	return false;  // didnt find the header or the version number
}

// could just unroll these below, or fold them into the strPackfileManager
static void sSetImageIsNew(u32 index)
{
	strPackfileManager::GetImageFile(index)->m_bNew = true; // CStreaming::SetImageIsNew
}

static const atFinalHashString& sGetImageFilename(u32 index)
{
	return strPackfileManager::GetImageFile(index)->m_name;
}


#if !__NO_OUTPUT
template <size_t _Size>
void GetDateAsString(u64 fileDate, char (&returnDateString)[_Size])
{
	// FILETIME is a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601.
	// Need to convert it to milliseconds since midnight (00:00:00), January 1, 1970, coordinated universal time (UTC).
	// 11644473600000 is a number of milliseconds between Jan 1, 1601 and Jan 1, 1970.
	u64 posixTimeMilliseconds64 = (fileDate / 10000) - 11644473600000ULL;

	u64 posixTimeSeconds64 = posixTimeMilliseconds64 / 1000;
	time_t posixTime = static_cast<time_t>(posixTimeSeconds64);
	struct tm* pTimeInfo;

	// Need to use thread-safe variant of localtime on Prospero
#if RSG_PROSPERO
	struct tm timeInfoData;
	pTimeInfo = localtime_s(&posixTime, &timeInfoData);
#else
	pTimeInfo = localtime(&posixTime);
#endif

	char MonthString[12][32] =
	{
		"Jan",
		"Feb",
		"Mar",
		"Apr",
		"May",
		"Jun",
		"Jul",
		"Aug",
		"Sep",
		"Oct",
		"Nov",
		"Dec"
	};

	if ((pTimeInfo->tm_mon >= 0) && (pTimeInfo->tm_mon < 12))
	{
		formatf(returnDateString, "%02d:%02d:%02d %02d %s %04d", 
			pTimeInfo->tm_hour, pTimeInfo->tm_min, pTimeInfo->tm_sec,
			pTimeInfo->tm_mday, MonthString[pTimeInfo->tm_mon], (1900 + pTimeInfo->tm_year) );
	}
	else
	{
		cacheloaderAssertf(0, "GetDateAsString - Month=%d. Expected it to be >=0 and < 12", pTimeInfo->tm_mon);

		formatf(returnDateString, "%02d:%02d:%02d %02d/%02/%04d",
			pTimeInfo->tm_hour, pTimeInfo->tm_min, pTimeInfo->tm_sec,
			pTimeInfo->tm_mday, pTimeInfo->tm_mon, (1900 + pTimeInfo->tm_year) );
	}
}
#endif // !__NO_OUTPUT


/////////////////////////////////////////////////////////////////////////////////////
bool strCacheLoader::CheckCacheFileDates()
{
	bool bAllFilesMatched = true;
	DEV_ONLY(const u32 scriptImageHash = atStringHash("platform:/levels/" RS_PROJECT "/script/script.rpf"));

#if !__FINAL && !(RSG_XBOX || RSG_SCE || RSG_PC)
	// don't modify cache when using title update folder
	const char* pUpdateFolder;
	if (PARAM_update.Get(pUpdateFolder))
	{
		return(true);			// force using the current cache
	}
#endif //__FINAL

	// If dates are not there then resave cache next time round
	if (ms_pFileDatesStart == NULL)
	{
		cacheloaderDisplayf("Cacheloader : No dates stored");
		return false;
	}

	// Build the file->timestamp map from the cache.dat parsed file
	const char* pFileDates = ms_pFileDatesStart;
	atMap<u32,u64> fileTimeStamps;
	char line[1024];
	while (pFileDates < ms_pFileDatesEnd)
	{
		u64 date;
		u32 fileNameHash;

		pFileDates = GetLine(pFileDates, line);
		if(cacheloaderVerify(sscanf(line, "%u %" I64FMT "d", &fileNameHash, &date) == 2))
		{
			cacheloaderAssertf(fileTimeStamps.Access(fileNameHash)==NULL, "Found duped hash entry: %X. Expect game cache to go crazy", fileNameHash);
			fileTimeStamps.Insert(fileNameHash,date);
		}
	}

	// Go through all file images
#if !__NO_OUTPUT
	atMap<u32,bool> warned;
#endif
	const s32 packfileCount = strPackfileManager::GetImageFileCount();
	for(s32 imageIndex=0; imageIndex < packfileCount; ++imageIndex)
	{
		// DLC files must be part of the allowed archives
		if (strCacheLoader::GetMode() == SCL_DLC)
		{
			if (ms_dlcArchives.Access(imageIndex) == NULL)
				continue;
		}
		else
		{
			// Ignore images from extra content
			if( strPackfileManager::GetImageFile(imageIndex)->m_extraDataIndex != 0 )
			{
				sSetImageIsNew(imageIndex);
				continue;
			}
		}

		// Make sure we skip dummy archive entries.
		if (!strPackfileManager::IsArchiveManaged(imageIndex))
		{
			continue;
		}

		const u32 fnHash = sGetImageFilename(imageIndex).GetHash();
		const u64* cachedDateTime = fileTimeStamps.Access(fnHash);

		// If the file wasn't in the cache, mark as new
		if (cachedDateTime == NULL)
		{
#if !__NO_OUTPUT
			if (!warned.Access(fnHash))
			{
				cacheloaderDisplayf("Cacheloader : %s is not in the cache. It must have been added to the game since the cache was last built", sGetImageFilename(imageIndex).GetCStr());
				warned.Insert(fnHash,true);
			}
#endif	// !__NO_OUTPUT

			if (!PARAM_nocachetimestamps.Get())
			{
				sSetImageIsNew(imageIndex);
				bAllFilesMatched = false;
			}

			continue;
		}

		// Otherwise, check if the timestamp matches
		// The timestamp seems to be the value of a FILETIME structure which is 
		// a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601
		const u64 fileDate = strPackfileManager::GetImageTime(imageIndex);
		const bool bSameTime = (fileDate == (*cachedDateTime));

		if ((!bSameTime && !PARAM_nocachetimestamps.Get()) DEV_ONLY(&& fnHash != scriptImageHash))
		{
#if !__NO_OUTPUT
			if (!warned.Access(fnHash))
			{
				char fileDateString[64];
				GetDateAsString(fileDate, fileDateString);
				cacheloaderDisplayf("Cacheloader : Streaming date is %s", fileDateString);

				char cacheDateString[64];
				GetDateAsString(*cachedDateTime, cacheDateString);
				cacheloaderDisplayf("Cacheloader : Cache date is %s", cacheDateString);

				cacheloaderDisplayf("Cacheloader : Streaming dates and cache dates differ for %s", sGetImageFilename(imageIndex).GetCStr());
				warned.Insert(fnHash,true);
			}
#endif	// !__NO_OUTPUT

			sSetImageIsNew(imageIndex);
			bAllFilesMatched = false;
			continue;
		}
	}

	return bAllFilesMatched;
}

/////////////////////////////////////////////////////////////////////////////////////
void strCacheLoader::WriteCacheFileDates()
{
	WriteStringToBuffer(CACHELOADER_FILEDATE_START_TOKEN"\n");
	for(s32 i=0; i<strPackfileManager::GetImageFileCount(); i++)
	{
		// DLC files must be part of the allowed archives
		if (strCacheLoader::GetMode() == SCL_DLC)
		{
			if (ms_dlcArchives.Access(i) == NULL)
				continue;
		}
		else
		{
			// ignore dummy archives and images from extra content
			if (strPackfileManager::GetImageFile(i)->m_extraDataIndex != 0)
			{
				continue;
			}
		}

		if (!strPackfileManager::IsArchiveManaged(i))
		{
			continue;
		}

		const u32 uNameHash = sGetImageFilename(i).GetHash();
		const u64 imageFileDate = strPackfileManager::GetImageTime(i);
		if ( uNameHash !=0 )
		{
			WriteStringToBuffer("%u %" I64FMT "d\n", uNameHash, imageFileDate);
			cacheloaderDebugf1("Cacheloader : Image %s timestamp is %" I64FMT "u", sGetImageFilename(i).GetCStr(), imageFileDate);
		}
	}
	WriteStringToBuffer(CACHELOADER_FILEDATE_END_TOKEN"\n");
}

/////////////////////////////////////////////////////////////////////////////////////
// name:		strCacheLoader::GetLine
// description:	Copy line into a temporary buffer and return pointer to next line
const char* strCacheLoader::GetLine(const char* pBuffer, char* pLine)
{
	s32 stringIndex = 0;
	while (*pBuffer != '\n' && *pBuffer != '\0')
	{
		pLine[stringIndex] = *pBuffer;
		pBuffer++;
		stringIndex++;
	}
	pLine[stringIndex] = '\0';
	pBuffer++;  // move onto start of next line
	return pBuffer;
}

/////////////////////////////////////////////////////////////////////////////////////
// NAME:	strCacheLoader::WriteHeader
// PURPOSE:	writes the current module header to the cache file
// RETURNS: NONE
/////////////////////////////////////////////////////////////////////////////////////
char* strCacheLoader::CModuleDescArray::WriteHeader(s32 module)
{
	s32 size=0;

	WriteStringToBuffer(CACHELOADER_MODULE_START_TOKEN"\n");
	WriteStringToBuffer("%s\n", m_modules[module].m_moduleName);
	char* pSize = ms_WorkBufferPos;
	WriteDataToBuffer(&size, 4);
	// DW - this clearly looks wrong ( and has been for a long time this legacy method of not writing the correct size for the module ) 
	// it seemed impossible to believe and understand how in fact it did ever work to start with, without having the time to go back in time and 
	// debug it the solution has been to retrospectively patch up this obviously incorrect size just before serialisation. ( Sorry I inherited this code and did not write it! )
	//	cacheloaderDisplayf("Cacheloader : Writing Module size ( clearly this is/has been wrong!) (%s = %d)", m_modules[module].m_moduleName, size);
	m_modules[module].m_header_pos = ms_WorkBufferPos;
	return pSize;
}

/////////////////////////////////////////////////////////////////////////////////////
void strCacheLoader::CModuleDescArray::WriteFooter(s32 module)
{
	m_modules[module].m_footer_pos = ms_WorkBufferPos;
	WriteStringToBuffer(CACHELOADER_MODULE_END_TOKEN"\n");
}

/////////////////////////////////////////////////////////////////////////////////////
char* strCacheLoader::CModuleDescArray::GetHeader(s32 module)
{
	return (m_modules[module].m_header_pos);
}

/////////////////////////////////////////////////////////////////////////////////////
char* strCacheLoader::CModuleDescArray::GetFooter(s32 module)
{
	return (m_modules[module].m_footer_pos);
}

/////////////////////////////////////////////////////////////////////////////////////
bool strCacheLoader::CModuleDescArray::Load(s32 index, fiStream* pTextFileToWriteTo)
{
	bool rt = true;
	char *pWorkBuff;

	// move buffer pointer to correct position in memory
	pWorkBuff = m_modules[index].m_header_pos;
	cacheloaderDisplayf("Cacheloader : Module %d Loaded from address %p to address %p", index, m_modules[index].m_header_pos, m_modules[index].m_footer_pos);

#if __BANK
	char outputString[256];

	if (pTextFileToWriteTo)
	{
		formatf(outputString, "%s - Start\n", m_modules[index].m_moduleName);
		pTextFileToWriteTo->Write(outputString, (s32) strlen(outputString));
	}
#endif // __BANK

	while (pWorkBuff < m_modules[index].m_footer_pos)
	{
		cacheloaderDebugf1("Cacheloader : Load Function called on address %p",pWorkBuff);

		rt = m_modules[index].m_loadFn(pWorkBuff, pTextFileToWriteTo);

		if (rt)
		{
			pWorkBuff += m_modules[index].m_entrySize;
		}
		else
		{
			SetCacheAsInvalid();  // we didnt get a good size so set cache as invalid
			ms_bLoadCache = false;
			ms_bSaveCache = true;
			cacheloaderAssertf(0, "Cache File Corrupt - Please correct by re-running the application");
			break;
		}
	}

#if __BANK
	if (pTextFileToWriteTo)
	{
		formatf(outputString, "%s - End\n\n", m_modules[index].m_moduleName);
		pTextFileToWriteTo->Write(outputString, (s32) strlen(outputString));
	}
#endif // __BANK

	return rt;
}



/////////////////////////////////////////////////////////////////////////////////////
// NAME:	strCacheLoader::WriteDataToBuffer
// PURPOSE:	copies the contents of pData to the cache work buffer
// RETURNS: NONE
/////////////////////////////////////////////////////////////////////////////////////
void strCacheLoader::WriteDataToBuffer(void* pData, s32 size)
{
	memcpy(ms_WorkBufferPos, pData, size);

	ms_WorkBufferPos += size;

    if ((ms_WorkBufferPos - ms_WorkBuffer) > ms_cacheBufferSize)
    {
	    Quitf(ERR_STR_CACHE_1,"Cache buffer(%dK) is too small for latest data - increase CACHELOADER_STANDARD_CACHE_BUFFER_SIZE", ms_cacheBufferSize/1024);
    }
}


/////////////////////////////////////////////////////////////////////////////////////
// NAME:	strCacheLoader::WriteStringToBuffer
// PURPOSE:	copies the contents of BufferString to the cache work buffer
// RETURNS: NONE
/////////////////////////////////////////////////////////////////////////////////////
void strCacheLoader::WriteStringToBuffer(const char* fmt, ...)
{
	char buffer[1024];

	va_list argptr;
	va_start(argptr, fmt);
	vformatf(buffer, 1024, fmt, argptr);

	memcpy(ms_WorkBufferPos, buffer, strlen(buffer));

	ms_WorkBufferPos += strlen(buffer);
	va_end(argptr);

    if ((ms_WorkBufferPos - ms_WorkBuffer) > ms_cacheBufferSize)
    {
        Quitf(ERR_STR_CACHE_2,"Cache buffer(%dK) is too small for latest data - increase CACHELOADER_STANDARD_CACHE_BUFFER_SIZE", ms_cacheBufferSize/1024);
    }
}

/////////////////////////////////////////////////////////////////////////////////////
void strCacheLoader::CModuleDescArray::Save(s32 index, fiStream* pTextFileToWriteTo)
{
	char outputString[256];

	if (pTextFileToWriteTo)
	{
		formatf(outputString, "%s - Start\n", m_modules[index].m_moduleName);
		pTextFileToWriteTo->Write(outputString, (s32)strlen(outputString));
	}

	char* pSize = WriteHeader(index);
	cacheloaderAssert(*((s32*)pSize)==0);
	char* posStart = ms_WorkBufferPos;
	m_modules[index].m_saveFn(pTextFileToWriteTo);
	char* posEnd = ms_WorkBufferPos;
	s32 size = ptrdiff_t_to_int(posEnd-posStart);
	memcpy(pSize, &size, 4);
	cacheloaderDisplayf("Cacheloader : module size is actually %d", size);

	WriteFooter(index);

	if (pTextFileToWriteTo)
	{
		formatf(outputString, "%s - End\n\n", m_modules[index].m_moduleName);
		pTextFileToWriteTo->Write(outputString, (s32)strlen(outputString));
	}
}


/////////////////////////////////////////////////////////////////////////////////////
void strCacheLoader::AllocateWorkBuffer()
{
	// no cache file, so create a block of memory to store the cache data later
	if (ms_mode == SCL_DLC)
	{
		MEM_USE_USERDATA(MEMUSERDATA_CACHE_LOADER);
		ms_WorkBuffer = (char*)strStreamingEngine::GetAllocator().Allocate(ms_cacheBufferSize, 16, MEMTYPE_RESOURCE_VIRTUAL);

		if (ms_WorkBuffer == NULL)
			Quitf(ERR_CACHE_ALLOC, "strCacheLoader::AllocateWorkBuffer - allocation failed!");
	}
	else
	{
		ms_WorkBuffer = rage_new char [ms_cacheBufferSize];
	}
	
	ms_WorkBufferPos = ms_WorkBuffer;

	cacheloaderDisplayf("buffer size =%dK", ms_cacheBufferSize/1024);
#if __DEV
	u32* cookie = (u32*)&ms_WorkBuffer[ms_cacheBufferSize-4];
	*cookie = 0xd34db33f;
#endif // _DEV
}

/////////////////////////////////////////////////////////////////////////////////////
void strCacheLoader::FreeWorkBuffer()
{
	cacheloaderDisplayf("Free work memory");
#if __DEV && __ASSERT
	u32* cookie = (u32*)&ms_WorkBuffer[ms_cacheBufferSize-4];
	cacheloaderAssert(*cookie == 0xd34db33f);
#endif 

	if (ms_mode == SCL_DLC)
	{
		MEM_USE_USERDATA(MEMUSERDATA_CACHE_LOADER);
		strStreamingEngine::GetAllocator().Free(ms_WorkBuffer);
	}
	else
	{
		delete[] ms_WorkBuffer;
	}
	
	ms_WorkBuffer = NULL;
	ms_WorkBufferPos = NULL;
}

/////////////////////////////////////////////////////////////////////////////////////
void strCacheLoader::BuildCacheFilename(const char* const pFilename)
{
	if (pFilename)
	{
		char baseName[RAGE_MAX_PATH];
		char ipAddress[256] = "";
		ASSET.BaseName(baseName, RAGE_MAX_PATH, pFilename);

#if !__RESOURCECOMPILER		// so ragebuilder doesn't incur a dependency on RageNet solely for this one function, which it doesn't even need.
		if(PARAM_CACHELOADER_SHAREDATA_COMMAND_LINE.Get())
		{	
			// Try to get the net address of this machine, so we can
			// make the cache file names unique to the console.
			netIpAddress ip;
			const bool success = netHardware::GetLocalIpAddress(&ip);
			if(success && ip.IsValid())
			{
				char tmp[netIpAddress::MAX_STRING_BUF_SIZE];
				sprintf(ipAddress, "_%s" , ip.Format(tmp));

				// some platforms don't like colons in the filename
				unsigned len = ustrlen(ipAddress);
				for(unsigned i = 0; i < len; ++i)
				{
					if(ipAddress[i] == ':')
					{
						ipAddress[i] = '_';
					}
				}
				cacheloaderDisplayf("Data is being shared out by a single source the cache file will be appended with %s", ipAddress);
			}
		}
#endif	// __RESOURCECOMPILER

		const char* deviceAndPath = "gamecache:/data/";

		if (ms_mode == SCL_DLC && Verifyf(strlen(ms_cDeviceAndPath) > 0, "BuildCacheFilename - ms_cDeviceAndPath is not valid!"))
			deviceAndPath = ms_cDeviceAndPath;

		if (ms_bPackagedBuild && ms_mode != SCL_DLC)
		{
			sprintf(
				ms_cCacheFilename,
				"%s%s_cache_packaged_%c%s%s.dat", deviceAndPath,
				ASSET.FileName(baseName),
				g_sysHostPlatform,
				(!__PACKEDDEBUG ? "_bank" : "" ),
				ipAddress
				);
		}
		else
		{
			sprintf(
				ms_cCacheFilename,
				"%s%s_cache_%c%s%s.dat", deviceAndPath,
				ASSET.FileName(baseName),
				g_sysHostPlatform,
				(!__PACKEDDEBUG ? "_bank" : "" ),
				ipAddress
			);
		}
	}
	else
	{
		sprintf(ms_cCacheFilename, CACHELOADER_STANDARD_CACHE_FILENAME);  // use standard filename
	}

#if !__FINAL && !__TOOL
	if (ms_mode == SCL_DLC && PARAM_buildDLCCacheData.Get())
	{		
		const fiDevice *device = fiDevice::GetDevice(ms_cCacheFilename);

		if (device)
		{
			char remoteCmd[256] = { 0 };
			char physicalPath[256] = { 0 };

			device->FixRelativeName(physicalPath, sizeof(physicalPath), ms_cCacheFilename);

			if (!ASSET.Exists(ms_cCacheFilename, NULL))
				ASSET.CreateLeadingPath(ms_cCacheFilename);

			formatf(remoteCmd, "p4 edit %s", physicalPath);
			sysExec(remoteCmd);
			formatf(remoteCmd, "p4 add -t binary %s", physicalPath);
			sysExec(remoteCmd);

			char filenameOfTextFile[RAGE_MAX_PATH];
			strcpy(filenameOfTextFile, physicalPath);
			ASSET.RemoveExtensionFromPath(filenameOfTextFile, RAGE_MAX_PATH, filenameOfTextFile);
			safecat(filenameOfTextFile, ".txt");

			formatf(remoteCmd, "p4 edit %s", filenameOfTextFile);
			sysExec(remoteCmd);
			formatf(remoteCmd, "p4 add %s", filenameOfTextFile);
			sysExec(remoteCmd);
		}
	}
#endif

	cacheloaderDisplayf("Cacheloader : file=%s enabled =%d", ms_cCacheFilename,ms_bCacheEnabled);
}

/////////////////////////////////////////////////////////////////////////////////////
void strCacheLoader::DetermineCacheFileReadOnly()
{
	const fiDevice* pDev = fiDevice::GetDevice(ms_cCacheFilename, false);
	//GetDevice never returns NULL so this check is kinda stupid
	if(pDev == NULL || pDev->IsRpf())
	{
		ms_bCacheReadonly = true;
	}
	else
	{
		u32 attrs = pDev->GetAttributes(ms_cCacheFilename);
		if ((attrs != FILE_ATTRIBUTE_INVALID) && (attrs & FILE_ATTRIBUTE_READONLY))
			ms_bCacheReadonly = true;
		//else
		//	ms_bCacheReadonly = false;
	}

#if !__FINAL && !(RSG_XBOX || RSG_SCE || RSG_PC)
	if (strCacheLoader::GetMode() != SCL_DLC)
	{
		// don't modify cache when using title update folder
		const char* pUpdateFolder;
		if (PARAM_update.Get(pUpdateFolder))
		{
			ms_bCacheReadonly = true;
		}
	}
#endif //__FINAL

	cacheloaderDisplayf("Cacheloader : cache file readonly =%d", ms_bCacheReadonly);
}

/////////////////////////////////////////////////////////////////////////////////////
// load into allocated temporary buffer
void strCacheLoader::LoadCache()
{
	//CFileMgr::SetDir("platform:/");
	ms_cache_stream = ASSET.Open(GetCacheFilename(),"");
	cacheloaderAssertf(ms_cache_stream!=NULL, "strCacheLoader::LoadCache - Cannot open Cache file '%s'! - consider either using -sharecachedata ( if you share data from one machine) or -disablecacheloader (if you want to disable this optional accelerator) ", GetCacheFilename());
	if(ms_cache_stream==NULL)
	{
		cacheloaderErrorf("Cacheloader : could not open cache file %s!",GetCacheFilename());
		return;
	}

	int size = ms_cache_stream->Size();
	if (size > ms_cacheBufferSize)
    {
        Quitf(ERR_STR_CACHE_3,"Cache file (%s) (%dK) is larger than maximum allowed size (%dK) - please increase CACHELOADER_STANDARD_CACHE_BUFFER_SIZE", GetCacheFilename(), size/1024, ms_cacheBufferSize/1024);
    }

	ms_cache_stream->Seek(CACHELOADER_HEADER_SIZE);// seek to end of header
	size -= CACHELOADER_HEADER_SIZE; // size of file includes this extra header content
	
	ms_cache_stream->Read(ms_WorkBuffer, size);
	ms_WorkBufferPos = ms_WorkBuffer + size;

	cacheloaderDisplayf("Cacheloader : Loading %dK into buffer sized %dK", size/1024, ms_cacheBufferSize/1024);
}

void strCacheLoader::WriteWorkBuffer()
{
	if(ms_cache_stream)
	{
		ms_cache_stream->Seek(CACHELOADER_HEADER_SIZE); // seek to end of header

		ms_cache_stream->Write(&ms_WorkBuffer[0], ptrdiff_t_to_int(ms_WorkBufferPos - &ms_WorkBuffer[0]));
	}
}

//////////////////////////////////////////////////////////////////////////////
// Encrypts the work buffer in place using datProtect
// Calls WriteSignature to write the calculated signature to the cache stream
void strCacheLoader::EncryptWorkBuffer()
{
	if(!ms_bCacheUsesEncryption)
		return;
	datProtectContext context(false, (u8*)"gtaCACHELOADER", 14);
	u8 signature[datProtectSignatureSize];
	cacheloaderDisplayf("Cacheloader : Encrypting %" PTRDIFFTFMT "u bytes", ms_WorkBufferPos - &ms_WorkBuffer[0]);
	datProtect((u8*)ms_WorkBuffer, ptrdiff_t_to_int(ms_WorkBufferPos - &ms_WorkBuffer[0]), context, signature);
	// Append signature to file header
	WriteSignature((char*)&signature);
}

//////////////////////////////////////////////////////////////////////////////
// Writes the datProtect signature to the cache file stream
void strCacheLoader::WriteSignature(char* signature)
{
	if(!ms_cache_stream || !ms_bCacheUsesEncryption)
		return;

	ms_cache_stream->Seek(CACHELOADER_HEADER_SIZE-datProtectSignatureSize); // seek to end of header minus the size of the signature
	
	ms_cache_stream->Write(signature,datProtectSignatureSize); // write signature
}

/////////////////////////////////////////////////////////////////////////////
// Decrypts the loaded cacheloader file in place, using the signature that is stored in the file
void strCacheLoader::DecryptWorkBuffer()
{
	if(!ms_bCacheUsesEncryption || !ms_cache_stream)
		return; // nothing to do

	datProtectContext context(false, (u8*)"gtaCACHELOADER", 14);
	u8 signature[datProtectSignatureSize];

	// Read the datProtect signature
	ReadSignature((char*)&signature);
	cacheloaderDisplayf("Cacheloader : Decryption %" PTRDIFFTFMT "u bytes", ms_WorkBufferPos - &ms_WorkBuffer[0]);
	bool success = datUnprotect((u8*)ms_WorkBuffer, ptrdiff_t_to_int(ms_WorkBufferPos - &ms_WorkBuffer[0]), context, signature);
	if(!success)
	{
		cacheloaderErrorf("Error Decrypting the cache file!");
		// Failed, don't use the cache - try to write a new one
		ms_bLoadCache = false;
		ms_bSaveCache = true;
	}
}

//////////////////////////////////////////////////////////////////////////////
// Reads the datProtect signature from the cache file stream
void strCacheLoader::ReadSignature(char* signature)
{
	if(!ms_cache_stream || !ms_bCacheUsesEncryption)
		return;
	// Read signature from file (last datProtectSignatureSize bytes of header)
	ms_cache_stream->Seek(CACHELOADER_HEADER_SIZE-datProtectSignatureSize);
	ms_cache_stream->Read(signature, datProtectSignatureSize);
}

} // namespace rage 
