/*
 *  Original work: Copyright (c) 2014, Oculus VR, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  RakNet License.txt file in the licenses directory of this source tree. An additional grant 
 *  of patent rights can be found in the RakNet Patents.txt file in the same directory.
 *
 *
 *  Modified work: Copyright (c) 2017, SLikeSoft UG (haftungsbeschränkt)
 *
 *  This source code was modified by SLikeSoft. Modifications are licensed under the MIT-style
 *  license found in the license.txt file in the root directory of this source tree.
 */

#include "AutopatcherClient.h"
#include "slikenet/DirectoryDeltaTransfer.h"
#include "slikenet/FileList.h"
#include "slikenet/StringCompressor.h"
#include "slikenet/peerinterface.h"
#include "slikenet/FileListTransfer.h"
#include "slikenet/FileListTransferCBInterface.h"
#include "slikenet/BitStream.h"
#include "slikenet/MessageIdentifiers.h"
#include "slikenet/AutopatcherPatchContext.h"
#include "ApplyPatch.h"
#include "slikenet/FileOperations.h"
//#include "slikenet/DR_SHA1.h"
#include <stdio.h>
#include "slikenet/FileOperations.h"
#include "slikenet/assert.h"
#include "slikenet/ThreadPool.h"
#include "slikenet/linux_adapter.h"
#include "slikenet/osx_adapter.h"

using namespace SLNet;

#include "slikenet/SuperFastHash.h"
static const unsigned HASH_LENGTH=4;

#define COPY_ON_RESTART_EXTENSION ".patched.tmp"

// -----------------------------------------------------------------

PatchContext AutopatcherClientCBInterface::ApplyPatchBase(const char *oldFilePath, char **newFileContents, unsigned int *newFileSize, char *patchContents, unsigned int patchSize, uint32_t patchAlgorithm)
{
	// unused parameters
	(void)patchAlgorithm;

	return ApplyPatchBSDiff(oldFilePath, newFileContents, newFileSize, patchContents, patchSize);
}

PatchContext AutopatcherClientCBInterface::ApplyPatchBSDiff(const char *oldFilePath, char **newFileContents, unsigned int *newFileSize, char *patchContents, unsigned int patchSize)
{
	FILE *fp;
	if (fopen_s(&fp, oldFilePath, "rb")!=0)
		return PC_ERROR_PATCH_TARGET_MISSING;

	fseek(fp, 0, SEEK_END);
	unsigned int prePatchLength = ftell(fp);
	fseek(fp, 0, SEEK_SET);
	char *prePatchFile = (char*) rakMalloc_Ex(prePatchLength, _FILE_AND_LINE_);
	fread(prePatchFile, prePatchLength, 1, fp);
	fclose(fp);

	bool result = ApplyPatch(prePatchFile, prePatchLength, newFileContents, newFileSize, patchContents, patchSize);
	rakFree_Ex(prePatchFile, _FILE_AND_LINE_);

	if (result==false)
		return PC_ERROR_PATCH_APPLICATION_FAILURE;
	
	return PC_WRITE_FILE;
}

// -----------------------------------------------------------------

struct AutopatcherClientThreadInfo
{
	FileListTransferCBInterface::OnFileStruct onFileStruct;
	char applicationDirectory[512];
	PatchContext result;
	// unsigned prePatchLength;
	// char *prePatchFile;

	// postPatchFile is passed in PC_NOTICE_WILL_COPY_ON_RESTART
	char *postPatchFile;
	unsigned postPatchLength;
	AutopatcherClientCBInterface *cbInterface;
};
// -----------------------------------------------------------------
AutopatcherClientThreadInfo* AutopatcherClientWorkerThread(AutopatcherClientThreadInfo* input, bool *returnOutput, void* perThreadData)
{
	// unused parameters
	(void)perThreadData;

	char fullPathToDir[1024];
	*returnOutput=true;

	strcpy_s(fullPathToDir, input->applicationDirectory);
	strcat_s(fullPathToDir, input->onFileStruct.fileName);
	if (input->onFileStruct.context.op==PC_WRITE_FILE)
	{
		if (WriteFileWithDirectories(fullPathToDir, (char*)input->onFileStruct.fileData, input->onFileStruct.byteLengthOfThisFile)==false)
		{
			char newDir[1024];
			strcpy_s(newDir, fullPathToDir);
			strcat_s(newDir, COPY_ON_RESTART_EXTENSION);
			if (WriteFileWithDirectories(newDir, (char*)input->onFileStruct.fileData, input->onFileStruct.byteLengthOfThisFile))
			{
				input->result=PC_NOTICE_WILL_COPY_ON_RESTART;
			}
			else
			{
				input->result=PC_ERROR_FILE_WRITE_FAILURE;
			}
		}
		else
		{
			input->result=(PatchContext) input->onFileStruct.context.op;
		}
	}
	else
	{
		RakAssert(input->onFileStruct.context.op==PC_HASH_1_WITH_PATCH || input->onFileStruct.context.op==PC_HASH_2_WITH_PATCH);

//		CSHA1 sha1;
		

		//				printf("apply patch %i bytes\n", byteLengthOfThisFile-SHA1_LENGTH);
		//				for (int i=0; i < byteLengthOfThisFile-SHA1_LENGTH; i++)
		//					printf("%i ", fileData[SHA1_LENGTH+i]);
		//				printf("\n");
		int hashMultiplier;
		if (input->onFileStruct.context.op==PC_HASH_1_WITH_PATCH)
			hashMultiplier=1;
		else
			hashMultiplier=2; // else op==PC_HASH_2_WITH_PATCH

		PatchContext result = input->cbInterface->ApplyPatchBase(fullPathToDir, &input->postPatchFile, &input->postPatchLength, (char*)input->onFileStruct.fileData+HASH_LENGTH*hashMultiplier, input->onFileStruct.byteLengthOfThisFile-HASH_LENGTH*hashMultiplier, input->onFileStruct.context.flnc_extraData2);
		if (result == PC_ERROR_PATCH_APPLICATION_FAILURE || input->result==PC_ERROR_PATCH_TARGET_MISSING)
		{
			input->result=result;
			return input;
		}

		unsigned int hash = SuperFastHash(input->postPatchFile, input->postPatchLength);
		if (SLNet::BitStream::DoEndianSwap())
			SLNet::BitStream::ReverseBytesInPlace((unsigned char*) &hash, sizeof(hash));

		//if (memcmp(sha1.GetHash(), input->onFileStruct.fileData, HASH_LENGTH)!=0)

		if (memcmp(&hash, input->onFileStruct.fileData+HASH_LENGTH*(hashMultiplier-1), HASH_LENGTH)!=0)
		{
			input->result=PC_ERROR_PATCH_RESULT_CHECKSUM_FAILURE;
		}
		else
		{
			// Write postPatchFile over the existing file
			if (WriteFileWithDirectories(fullPathToDir, (char*)input->postPatchFile, input->postPatchLength)==false)
			{
				char newDir[1024];
				strcpy_s(newDir, fullPathToDir);
				strcat_s(newDir, COPY_ON_RESTART_EXTENSION);
				if (WriteFileWithDirectories(newDir, (char*)input->postPatchFile, input->postPatchLength))
				{
					input->result=PC_NOTICE_WILL_COPY_ON_RESTART;
				}
				else
				{
					input->result=PC_ERROR_FILE_WRITE_FAILURE;
				}
			}
			else
			{
				input->result=(PatchContext)input->onFileStruct.context.op;
			}
		}
	}

	return input;
}
// -----------------------------------------------------------------
namespace SLNet
{
class AutopatcherClientCallback : public FileListTransferCBInterface
{
public:
	ThreadPool<AutopatcherClientThreadInfo*,AutopatcherClientThreadInfo*> threadPool;
	char applicationDirectory[512];
	AutopatcherClientCBInterface *onFileCallback;
	AutopatcherClient *client;
	bool downloadComplete;
	bool canDeleteUser;

	AutopatcherClientCallback(void)
	{
		threadPool.StartThreads(1,0);
		canDeleteUser=false;
		downloadComplete=false;
	}
	virtual ~AutopatcherClientCallback(void)
	{
		StopThreads();
	}
	void StopThreads(void)
	{
		threadPool.StopThreads();
		RakAssert(threadPool.NumThreadsWorking()==0);

		unsigned i;
		AutopatcherClientThreadInfo* info;
		for (i=0; i < threadPool.InputSize(); i++)
		{
			info = threadPool.GetInputAtIndex(i);
//			if (info->prePatchFile)
//				rakFree_Ex(info->prePatchFile, _FILE_AND_LINE_ );
			if (info->postPatchFile)
				rakFree_Ex(info->postPatchFile, _FILE_AND_LINE_ );
			if (info->onFileStruct.fileData)
				rakFree_Ex(info->onFileStruct.fileData, _FILE_AND_LINE_ );
			SLNet::OP_DELETE(info, _FILE_AND_LINE_);
		}
		threadPool.ClearInput();
		for (i=0; i < threadPool.OutputSize(); i++)
		{
			info = threadPool.GetOutputAtIndex(i);
//			if (info->prePatchFile)
//				rakFree_Ex(info->prePatchFile, _FILE_AND_LINE_ );
			if (info->postPatchFile)
				rakFree_Ex(info->postPatchFile, _FILE_AND_LINE_ );
			if (info->onFileStruct.fileData)
				rakFree_Ex(info->onFileStruct.fileData, _FILE_AND_LINE_ );
			SLNet::OP_DELETE(info, _FILE_AND_LINE_);
		}
		threadPool.ClearOutput();
	}
	// Update is run in the user thread
	virtual bool Update(void)
	{
		if (threadPool.HasOutputFast() && threadPool.HasOutput())
		{
			AutopatcherClientThreadInfo *threadInfo = threadPool.GetOutput();
			// #med - add proper range-check for threadInfo->result
			threadInfo->onFileStruct.context.op=static_cast<unsigned char>(threadInfo->result);
			switch (threadInfo->result)
			{
				case PC_NOTICE_WILL_COPY_ON_RESTART:
				{
					client->CopyAndRestart(threadInfo->onFileStruct.fileName);
					if (threadInfo->onFileStruct.context.op==PC_WRITE_FILE)
					{
						// Regular file in use but we can write the temporary file.  Restart and copy it over the existing
						onFileCallback->OnFile(&threadInfo->onFileStruct);
					}
					else
					{
						// Regular file in use but we can write the temporary file.  Restart and copy it over the existing
						rakFree_Ex(threadInfo->onFileStruct.fileData, _FILE_AND_LINE_ );
						threadInfo->onFileStruct.fileData=threadInfo->postPatchFile;
						onFileCallback->OnFile(&threadInfo->onFileStruct);
						threadInfo->onFileStruct.fileData=0;
					}
				}
				break;
				case PC_ERROR_FILE_WRITE_FAILURE:
				{
					if (threadInfo->onFileStruct.context.op==PC_WRITE_FILE)
					{
						onFileCallback->OnFile(&threadInfo->onFileStruct);
					}
					else
					{
						rakFree_Ex(threadInfo->onFileStruct.fileData, _FILE_AND_LINE_ );
						threadInfo->onFileStruct.fileData=threadInfo->postPatchFile;
						threadInfo->onFileStruct.byteLengthOfThisFile=threadInfo->postPatchLength;
						onFileCallback->OnFile(&threadInfo->onFileStruct);
						threadInfo->onFileStruct.fileData=0;
					}					
				}
				break;
				case PC_ERROR_PATCH_TARGET_MISSING:
				{
					onFileCallback->OnFile(&threadInfo->onFileStruct);
					client->Redownload(threadInfo->onFileStruct.fileName);
				}
				break;
				case PC_ERROR_PATCH_APPLICATION_FAILURE:
				{
					// Failure - signal class and download this file.
					onFileCallback->OnFile(&threadInfo->onFileStruct);
					client->Redownload(threadInfo->onFileStruct.fileName);
				}
				break;
				case PC_ERROR_PATCH_RESULT_CHECKSUM_FAILURE:
				{
					// Failure - signal class and download this file.
					onFileCallback->OnFile(&threadInfo->onFileStruct);
					client->Redownload(threadInfo->onFileStruct.fileName);
				}
				break;
				default:
				{
					if (threadInfo->onFileStruct.context.op==PC_WRITE_FILE)
					{
						onFileCallback->OnFile(&threadInfo->onFileStruct);
					}
					else
					{
						rakFree_Ex(threadInfo->onFileStruct.fileData, _FILE_AND_LINE_ );
						threadInfo->onFileStruct.fileData=threadInfo->postPatchFile;
						onFileCallback->OnFile(&threadInfo->onFileStruct);
						threadInfo->onFileStruct.fileData=0;
					}
				}
				break;
			}

//			if (threadInfo->prePatchFile)
//				rakFree_Ex(threadInfo->prePatchFile, _FILE_AND_LINE_ );
			if (threadInfo->postPatchFile)
				rakFree_Ex(threadInfo->postPatchFile, _FILE_AND_LINE_ );
			if (threadInfo->onFileStruct.fileData)
				rakFree_Ex(threadInfo->onFileStruct.fileData, _FILE_AND_LINE_ );
			SLNet::OP_DELETE(threadInfo, _FILE_AND_LINE_);
		}

		// If both input and output are empty, we are done.
		if (onFileCallback->Update()==false)
			canDeleteUser=true;

		if ( downloadComplete &&
			canDeleteUser &&
			threadPool.IsWorking()==false)
		{
			// Stop threads before calling OnThreadCompletion, in case the other thread starts a new instance of this thread.
			StopThreads();
			client->OnThreadCompletion();
			return false;
		}

		return true;
	}
	virtual bool OnDownloadComplete(DownloadCompleteStruct *dcs)
	{
		downloadComplete=true;
		if (onFileCallback->OnDownloadComplete(dcs)==false)
		{
			canDeleteUser=true;
		}
		return true;
	};
	virtual void OnDereference(void)
	{
		onFileCallback->OnDereference();
		StopThreads();
	}
	virtual bool OnFile(OnFileStruct *onFileStruct)
	{
		AutopatcherClientThreadInfo *inStruct = SLNet::OP_NEW<AutopatcherClientThreadInfo>( _FILE_AND_LINE_ );
		memset(inStruct,0,sizeof(AutopatcherClientThreadInfo));
//		inStruct->prePatchFile=0;
		inStruct->postPatchFile=0;
		inStruct->cbInterface=onFileCallback;
		memcpy(&(inStruct->onFileStruct), onFileStruct, sizeof(OnFileStruct));
		strcpy_s(inStruct->applicationDirectory,applicationDirectory);
		if (onFileStruct->context.op==PC_HASH_1_WITH_PATCH || onFileStruct->context.op==PC_HASH_2_WITH_PATCH)
			onFileStruct->context.op=PC_NOTICE_FILE_DOWNLOADED_PATCH;
		else
			onFileStruct->context.op=PC_NOTICE_FILE_DOWNLOADED;
		onFileCallback->OnFile(onFileStruct);
		threadPool.AddInput(AutopatcherClientWorkerThread, inStruct);

		// Return false means don't delete OnFileStruct::data
		return false;
	}
	virtual void OnFileProgress(FileProgressStruct *fps)
	{
		char fullPathToDir[1024];

		if (fps->onFileStruct->fileName)
		{
			strcpy_s(fullPathToDir, applicationDirectory);
			strcat_s(fullPathToDir, fps->onFileStruct->fileName);
			onFileCallback->OnFileProgress(fps);
		}
	}
};
}
AutopatcherClient::AutopatcherClient()
{
	serverId=UNASSIGNED_SYSTEM_ADDRESS;
	// #med - use unsigned short max?
	serverIdIndex=static_cast<unsigned short>(-1);
	applicationDirectory[0]=0;
	fileListTransfer=0;
    priority=HIGH_PRIORITY;
	orderingChannel=0;
	serverDate=0;
	userCB=0;
	processThreadCompletion=false;
}
AutopatcherClient::~AutopatcherClient()
{
	Clear();
}
void AutopatcherClient::Clear(void)
{
	if (fileListTransfer)
		fileListTransfer->RemoveReceiver(serverId);
	serverId=UNASSIGNED_SYSTEM_ADDRESS;
	setId=(unsigned short)-1;
	redownloadList.Clear();
	copyAndRestartList.Clear();
}
void AutopatcherClient::SetUploadSendParameters(PacketPriority _priority, char _orderingChannel)
{
	priority=_priority;
	orderingChannel=_orderingChannel;
}
void AutopatcherClient::SetFileListTransferPlugin(FileListTransfer *flt)
{
	fileListTransfer=flt;
}
double AutopatcherClient::GetServerDate(void) const
{
	return serverDate;
}
void AutopatcherClient::CancelDownload(void)
{
	fileListTransfer->CancelReceive(setId);
	Clear();
}
void AutopatcherClient::OnThreadCompletion(void)
{
	processThreadCompletionMutex.Lock();
	processThreadCompletion=true;
	processThreadCompletionMutex.Unlock();
}
bool AutopatcherClient::IsPatching(void) const
{
	return fileListTransfer->IsHandlerActive(setId);
}
bool AutopatcherClient::PatchApplication(const char *_applicationName, const char *_applicationDirectory, double lastUpdateDate, SystemAddress host, AutopatcherClientCBInterface *onFileCallback, const char *restartOutputFilename, const char *pathToRestartExe)
{
    RakAssert(applicationName);
	RakAssert(applicationDirectory);
	RakAssert(pathToRestartExe);
	RakAssert(restartOutputFilename);

//	if (rakPeerInterface->GetIndexFromSystemAddress(host)==-1)
//		return false;
	if (IsPatching())
		return false; // Already in the middle of patching.

	strcpy_s(applicationDirectory, _applicationDirectory);
	FileList::FixEndingSlash(applicationDirectory, 512);
	strcpy_s(applicationName, _applicationName);
	serverId=host;
	patchComplete=false;
	userCB=onFileCallback;
	strcpy_s(copyOnRestartOut, restartOutputFilename);
	strcpy_s(restartExe, pathToRestartExe);
	processThreadCompletionMutex.Lock();
	processThreadCompletion=false;
	processThreadCompletionMutex.Unlock();

	SLNet::BitStream outBitStream;
	outBitStream.Write((unsigned char)ID_AUTOPATCHER_GET_CHANGELIST_SINCE_DATE);
	StringCompressor::Instance()->EncodeString(applicationName, 512, &outBitStream);
	outBitStream.Write(lastUpdateDate);
    SendUnified(&outBitStream, priority, RELIABLE_ORDERED, orderingChannel, host, false);
	return true;
}
void AutopatcherClient::Update(void)
{
	processThreadCompletionMutex.Lock();
	if (processThreadCompletion)
	{
		processThreadCompletion=false;
		processThreadCompletionMutex.Unlock();

		fileListTransfer->RemoveReceiver(serverId);

		// If redownload list, process it
		if (redownloadList.fileList.Size())
		{
			SLNet::BitStream outBitStream;
			AutopatcherClientCallback *transferCallback;
			transferCallback = SLNet::OP_NEW<AutopatcherClientCallback>( _FILE_AND_LINE_ );
			strcpy_s(transferCallback->applicationDirectory, applicationDirectory);
			transferCallback->onFileCallback=userCB;
			transferCallback->client=this;
			setId = fileListTransfer->SetupReceive(transferCallback, true, serverId);

			// Ask for patches for the files in the list that are different from what we have.
			outBitStream.Write((unsigned char)ID_AUTOPATCHER_GET_PATCH);
			outBitStream.Write(setId);
			double lastUpdateData=0;
			outBitStream.Write(lastUpdateData);
			StringCompressor::Instance()->EncodeString(applicationName, 512, &outBitStream);
			redownloadList.Serialize(&outBitStream);
			SendUnified(&outBitStream, priority, RELIABLE_ORDERED, orderingChannel, serverId, false);
			redownloadList.Clear();
		}
		else if (copyAndRestartList.fileList.Size())
		{
			Packet *p = AllocatePacketUnified(1);
			p->bitSize=p->length*8;
			p->data[0]=ID_AUTOPATCHER_RESTART_APPLICATION;
			p->systemAddress=serverId;
			p->systemAddress.systemIndex=serverIdIndex;
			PushBackPacketUnified(p,false);

			FILE *fp;
			errno_t error = fopen_s(&fp, copyOnRestartOut, "wt");
			RakAssert(error == 0);
			if (error == 0)
			{
				fprintf(fp, "#Sleep 1000\n");
				unsigned i;
				for (i=0; i < copyAndRestartList.fileList.Size(); i++)
				{
#ifdef _WIN32
					fprintf(fp, "del /q \"%s%s\"\n", applicationDirectory, copyAndRestartList.fileList[i].filename.C_String());
					RakString sourceFn = copyAndRestartList.fileList[i].filename;
					RakString bareFilename = sourceFn;
					bareFilename.StartAfterLastCharacter('/');
					fprintf(fp, "rename \"%s%s%s\" \"%s\"\n", applicationDirectory, bareFilename.C_String(), COPY_ON_RESTART_EXTENSION, copyAndRestartList.fileList[i].filename.C_String());
#else
					fprintf(fp, "rm -f \"%s%s\"\n", applicationDirectory, copyAndRestartList.fileList[i].filename.C_String());
					fprintf(fp, "mv \"%s%s%s\" \"%s\"\n", applicationDirectory, copyAndRestartList.fileList[i].filename.C_String(), COPY_ON_RESTART_EXTENSION, copyAndRestartList.fileList[i].filename.C_String());
#endif
				}
#ifdef _WIN32
				fprintf(fp, "#CreateProcess \"%s\"\n", restartExe);
#else
				fprintf(fp, "chmod +x \"%s\"\n", restartExe);
				fprintf(fp, "#CreateProcess \"%s\" &\n", restartExe);
#endif
				fprintf(fp, "#DeleteThisFile\n");
				fclose(fp);
			}
		}
		else
		{
			Packet *p = AllocatePacketUnified(1);
			p->bitSize=p->length*8;
			p->data[0]=ID_AUTOPATCHER_FINISHED;
			p->systemAddress=serverId;
			p->systemAddress.systemIndex=serverIdIndex;
			PushBackPacketUnified(p,false);
		}
	}
	else
	{
		processThreadCompletionMutex.Unlock();
	}
}
void AutopatcherClient::OnClosedConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason )
{
	// unused parameters
	(void)rakNetGUID;
	(void)lostConnectionReason;

	if (systemAddress==serverId)
		Clear();
}
PluginReceiveResult AutopatcherClient::OnReceive(Packet *packet)
{
	switch (packet->data[0]) 
	{
	case ID_AUTOPATCHER_CREATION_LIST:
		return OnCreationList(packet);
	case ID_AUTOPATCHER_DELETION_LIST:
		OnDeletionList(packet);
		return RR_STOP_PROCESSING_AND_DEALLOCATE;
	case ID_AUTOPATCHER_REPOSITORY_FATAL_ERROR:
	case ID_AUTOPATCHER_CANNOT_DOWNLOAD_ORIGINAL_UNMODIFIED_FILES:
		fileListTransfer->RemoveReceiver(serverId);
		Clear();
		return RR_CONTINUE_PROCESSING;
	case ID_AUTOPATCHER_FINISHED_INTERNAL:
		return OnDownloadFinishedInternal(packet);
	case ID_AUTOPATCHER_FINISHED:
		return OnDownloadFinished(packet);
	}
	return RR_CONTINUE_PROCESSING;
}
void AutopatcherClient::OnShutdown(void)
{
	// TODO
}

PluginReceiveResult AutopatcherClient::OnCreationList(Packet *packet)
{
	RakAssert(fileListTransfer);
	if (packet->systemAddress!=serverId)
		return RR_STOP_PROCESSING_AND_DEALLOCATE;

	SLNet::BitStream inBitStream(packet->data, packet->length, false);
	SLNet::BitStream outBitStream;
	FileList remoteFileList, missingOrChanged;
	inBitStream.IgnoreBits(8);
	if (remoteFileList.Deserialize(&inBitStream)==false)
		return RR_STOP_PROCESSING_AND_DEALLOCATE;

	inBitStream.Read(serverDate);
	double patchApplicationLastUpdateDate;
	inBitStream.Read(patchApplicationLastUpdateDate);

	// Go through the list of hashes.  For each file we already have, remove it from the list.
	remoteFileList.ListMissingOrChangedFiles(applicationDirectory, &missingOrChanged, true, false);

	if (missingOrChanged.fileList.Size()==0)
	{
		packet->data[0]=ID_AUTOPATCHER_FINISHED;
		return RR_CONTINUE_PROCESSING; // Pass to user
	}

	// Prepare the transfer plugin to get a file list.
	AutopatcherClientCallback *transferCallback;
	transferCallback = SLNet::OP_NEW<AutopatcherClientCallback>( _FILE_AND_LINE_ );
	strcpy_s(transferCallback->applicationDirectory, applicationDirectory);
	transferCallback->onFileCallback=userCB;
	transferCallback->client=this;
	setId = fileListTransfer->SetupReceive(transferCallback, true, packet->systemAddress);

	// Ask for patches for the files in the list that are different from what we have.
	outBitStream.Write((unsigned char)ID_AUTOPATCHER_GET_PATCH);
	outBitStream.Write(setId);
	outBitStream.Write(patchApplicationLastUpdateDate);
	StringCompressor::Instance()->EncodeString(applicationName, 512, &outBitStream);
	missingOrChanged.Serialize(&outBitStream);
	SendUnified(&outBitStream, priority, RELIABLE_ORDERED, orderingChannel, packet->systemAddress, false);

	return RR_STOP_PROCESSING_AND_DEALLOCATE; // Absorb this message
}
void AutopatcherClient::OnDeletionList(Packet *packet)
{
	if (packet->systemAddress!=serverId)
		return;

	SLNet::BitStream inBitStream(packet->data, packet->length, false);
	SLNet::BitStream outBitStream;
	inBitStream.IgnoreBits(8);
	FileList fileList;
	if (fileList.Deserialize(&inBitStream)==false)
		return;
	fileList.DeleteFiles(applicationDirectory);
}
PluginReceiveResult AutopatcherClient::OnDownloadFinished(Packet *packet)
{
	SLNet::BitStream inBitStream(packet->data, packet->length, false);
	inBitStream.IgnoreBits(8);
	// This may have been created internally, with no serverDate written (line 469 or so)
	if (inBitStream.GetNumberOfUnreadBits()>7)
	{
		inBitStream.Read(serverDate);
	}
	serverId=packet->systemAddress;
	serverIdIndex=packet->systemAddress.systemIndex;

	return RR_CONTINUE_PROCESSING;
}
PluginReceiveResult AutopatcherClient::OnDownloadFinishedInternal(Packet *packet)
{
	SLNet::BitStream inBitStream(packet->data, packet->length, false);
	inBitStream.IgnoreBits(8);
	serverId=packet->systemAddress;
	serverIdIndex=packet->systemAddress.systemIndex;
	inBitStream.Read(serverDate);

	return RR_STOP_PROCESSING_AND_DEALLOCATE;
}
void AutopatcherClient::CopyAndRestart(const char *filePath)
{
	// We weren't able to write applicationDirectory + filePath so we wrote applicationDirectory + filePath + COPY_ON_RESTART_EXTENSION instead
	copyAndRestartList.AddFile(filePath,filePath, 0, 0, 0, FileListNodeContext(0,0,0,0));
}
void AutopatcherClient::Redownload(const char *filePath)
{
	redownloadList.AddFile(filePath,filePath, 0, 0, 0, FileListNodeContext(0,0,0,0));
}
