#include "AutopatcherClient.h"
#include "DirectoryDeltaTransfer.h"
#include "FileList.h"
#include "StringCompressor.h"
#include "RakPeerInterface.h"
#include "FileListTransfer.h"
#include "FileListTransferCBInterface.h"
#include "BitStream.h"
#include "PacketEnumerations.h"
#include "AutopatcherPatchContext.h"
#include "ApplyPatch.h"
#include "FileOperations.h"
#include "SHA1.h"
#include <stdio.h>
#include "FileOperations.h"
#ifdef _MSC_VER
#pragma warning( push )
#endif
#define COPY_ON_RESTART_EXTENSION ".patched.tmp"
class AutopatcherClientCallback : public FileListTransferCBInterface
{
public:
char applicationDirectory[512];
FileListTransferCBInterface *onFileCallback;
AutopatcherClient *client;
virtual void OnFile(
unsigned fileIndex,
char *filename,
char *fileData,
unsigned compressedTransmissionLength,
unsigned finalDataLength,
unsigned short setID,
unsigned setCount,
unsigned setTotalCompressedTransmissionLength,
unsigned setTotalFinalLength,
unsigned char context)
{
char fullPathToDir[1024];
if (filename && fileData)
{
strcpy(fullPathToDir, applicationDirectory);
strcat(fullPathToDir, filename);
if (context==PC_WRITE_FILE)
{
if (WriteFileWithDirectories(fullPathToDir, (char*)fileData, finalDataLength)==false)
{
char newDir[1024];
strcpy(newDir, fullPathToDir);
strcat(newDir, COPY_ON_RESTART_EXTENSION);
if (WriteFileWithDirectories(newDir, (char*)fileData, finalDataLength))
{
// Regular file in use but we can write the temporary file. Restart and copy it over the existing
client->CopyAndRestart(filename);
onFileCallback->OnFile(fileIndex, fullPathToDir, fileData, compressedTransmissionLength, finalDataLength, setID, setCount, setTotalCompressedTransmissionLength, setTotalFinalLength, PC_NOTICE_WILL_COPY_ON_RESTART);
}
else
onFileCallback->OnFile(fileIndex, fullPathToDir, fileData, compressedTransmissionLength, finalDataLength, setID, setCount, setTotalCompressedTransmissionLength, setTotalFinalLength, PC_ERROR_FILE_WRITE_FAILURE);
}
else
onFileCallback->OnFile(fileIndex, fullPathToDir, fileData, compressedTransmissionLength, finalDataLength, setID, setCount, setTotalCompressedTransmissionLength, setTotalFinalLength, context);
}
else
{
assert(context==PC_HASH_WITH_PATCH);
CSHA1 sha1;
FILE *fp;
unsigned prePatchLength, postPatchLength;
char *prePatchFile, *postPatchFile;
fp=fopen(fullPathToDir, "rb");
if (fp==0)
{
onFileCallback->OnFile(fileIndex, fullPathToDir, fileData, compressedTransmissionLength, finalDataLength, setID, setCount, setTotalCompressedTransmissionLength, setTotalFinalLength, PC_ERROR_PATCH_TARGET_MISSING);
client->Redownload(filename);
return;
}
fseek(fp, 0, SEEK_END);
prePatchLength = ftell(fp);
fseek(fp, 0, SEEK_SET);
prePatchFile= new char [prePatchLength];
fread(prePatchFile, prePatchLength, 1, fp);
fclose(fp);
// printf("apply patch %i bytes\n", finalDataLength-SHA1_LENGTH);
// for (int i=0; i < finalDataLength-SHA1_LENGTH; i++)
// printf("%i ", fileData[SHA1_LENGTH+i]);
// printf("\n");
if (ApplyPatch((char*)prePatchFile, prePatchLength, &postPatchFile, &postPatchLength, (char*)fileData+SHA1_LENGTH, finalDataLength-SHA1_LENGTH)==false)
{
delete [] prePatchFile;
// Failure - signal class and download this file.
onFileCallback->OnFile(fileIndex, fullPathToDir, fileData, compressedTransmissionLength, finalDataLength, setID, setCount, setTotalCompressedTransmissionLength, setTotalFinalLength, PC_ERROR_PATCH_APPLICATION_FAILURE);
client->Redownload(filename);
return;
}
sha1.Reset();
sha1.Update((unsigned char*) postPatchFile, postPatchLength);
sha1.Final();
if (memcmp(sha1.GetHash(), fileData, SHA1_LENGTH)!=0)
{
delete [] postPatchFile;
delete [] prePatchFile;
// Failure - signal class and download this file.
onFileCallback->OnFile(fileIndex, fullPathToDir, fileData, compressedTransmissionLength, finalDataLength, setID, setCount, setTotalCompressedTransmissionLength, setTotalFinalLength, PC_ERROR_PATCH_RESULT_CHECKSUM_FAILURE);
client->Redownload(filename);
return;
}
// Write postPatchFile over the existing file
if (WriteFileWithDirectories(fullPathToDir, (char*)postPatchFile, postPatchLength)==false)
{
char newDir[1024];
strcpy(newDir, fullPathToDir);
strcat(newDir, ".copy_on_restart.tmp");
if (WriteFileWithDirectories(newDir, (char*)postPatchFile, postPatchLength))
{
// Regular file in use but we can write the temporary file. Restart and copy it over the existing
client->CopyAndRestart(filename);
onFileCallback->OnFile(fileIndex, fullPathToDir, (char*) postPatchFile, compressedTransmissionLength, postPatchLength, setID, setCount, setTotalCompressedTransmissionLength, setTotalFinalLength, PC_NOTICE_WILL_COPY_ON_RESTART);
}
else
onFileCallback->OnFile(fileIndex, fullPathToDir, (char*) postPatchFile, compressedTransmissionLength, postPatchLength, setID, setCount, setTotalCompressedTransmissionLength, setTotalFinalLength, PC_ERROR_FILE_WRITE_FAILURE);
}
else
onFileCallback->OnFile(fileIndex, fullPathToDir, (char*) postPatchFile, compressedTransmissionLength, postPatchLength, setID, setCount, setTotalCompressedTransmissionLength, setTotalFinalLength, context);
delete [] postPatchFile;
delete [] prePatchFile;
}
}
}
virtual void OnFileProgress(unsigned fileIndex,
char *filename,
unsigned compressedTransmissionLength,
unsigned finalDataLength,
unsigned short setID,
unsigned setCount,
unsigned setTotalCompressedTransmissionLength,
unsigned setTotalFinalLength,
unsigned char context,
unsigned int partCount,
unsigned int partTotal,
unsigned int partLength)
{
char fullPathToDir[1024];
if (filename)
{
strcpy(fullPathToDir, applicationDirectory);
strcat(fullPathToDir, filename);
onFileCallback->OnFileProgress(fileIndex, fullPathToDir, compressedTransmissionLength, finalDataLength, setID, setCount, setTotalCompressedTransmissionLength, setTotalFinalLength, context, partCount, partTotal, partLength);
}
}
};
AutopatcherClient::AutopatcherClient()
{
rakPeer=0;
serverId=UNASSIGNED_PLAYER_ID;
applicationDirectory[0]=0;
fileListTransfer=0;
priority=HIGH_PRIORITY;
orderingChannel=0;
serverDate[0]=0;
}
AutopatcherClient::~AutopatcherClient()
{
Clear();
}
void AutopatcherClient::Clear(void)
{
if (fileListTransfer)
fileListTransfer->RemoveReceiver(serverId);
serverId=UNASSIGNED_PLAYER_ID;
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;
}
char* AutopatcherClient::GetServerDate(void) const
{
return (char*)serverDate;
}
bool AutopatcherClient::PatchApplication(const char *_applicationName, const char *_applicationDirectory, const char *lastUpdateDate, PlayerID host, FileListTransferCBInterface *onFileCallback, const char *restartOutputFilename, const char *pathToRestartExe)
{
assert(applicationName);
assert(applicationDirectory);
assert(rakPeer);
assert(pathToRestartExe);
assert(restartOutputFilename);
if (rakPeer->GetIndexFromPlayerID(host)==-1)
return false;
strcpy(applicationDirectory, _applicationDirectory);
if (IsSlash(applicationDirectory[strlen(applicationDirectory)-1])==false)
strcat(applicationDirectory, "\\");
strcpy(applicationName, _applicationName);
serverId=host;
cb=onFileCallback;
strcpy(copyOnRestartOut, restartOutputFilename);
strcpy(restartExe, pathToRestartExe);
RakNet::BitStream outBitStream;
outBitStream.Write((unsigned char)ID_AUTOPATCHER_GET_CHANGELIST_SINCE_DATE);
stringCompressor->EncodeString(applicationName, 512, &outBitStream);
stringCompressor->EncodeString(lastUpdateDate, 64, &outBitStream);
rakPeer->Send(&outBitStream, priority, RELIABLE_ORDERED, orderingChannel, host, false);
return true;
}
void AutopatcherClient::OnAttach(RakPeerInterface *peer)
{
rakPeer=peer;
}
#ifdef _MSC_VER
#pragma warning( disable : 4100 ) // warning C4100: <variable name> : unreferenced formal parameter
#endif
void AutopatcherClient::Update(RakPeerInterface *peer)
{
}
PluginReceiveResult AutopatcherClient::OnReceive(RakPeerInterface *peer, Packet *packet)
{
switch (packet->data[0])
{
case ID_CONNECTION_LOST:
case ID_DISCONNECTION_NOTIFICATION:
if (packet->playerId==serverId)
Clear();
case ID_AUTOPATCHER_CREATION_LIST:
return OnCreationList(peer, packet);
case ID_AUTOPATCHER_DELETION_LIST:
OnDeletionList(peer, packet);
return RR_STOP_PROCESSING_AND_DEALLOCATE;
case ID_AUTOPATCHER_REPOSITORY_FATAL_ERROR:
fileListTransfer->RemoveReceiver(serverId);
Clear();
return RR_CONTINUE_PROCESSING;
case ID_AUTOPATCHER_FINISHED:
return OnAutopatcherFinished(peer, packet);
}
return RR_CONTINUE_PROCESSING;
}
#ifdef _MSC_VER
#pragma warning( disable : 4100 ) // warning C4100: <variable name> : unreferenced formal parameter
#endif
void AutopatcherClient::OnDisconnect(RakPeerInterface *peer)
{
}
PluginReceiveResult AutopatcherClient::OnCreationList(RakPeerInterface *peer, Packet *packet)
{
assert(fileListTransfer);
if (packet->playerId!=serverId)
return RR_STOP_PROCESSING_AND_DEALLOCATE;
RakNet::BitStream inBitStream(packet->data, packet->length, false);
RakNet::BitStream outBitStream;
FileList remoteFileList, missingOrChanged;
inBitStream.IgnoreBits(8);
if (remoteFileList.Deserialize(&inBitStream)==false)
return RR_STOP_PROCESSING_AND_DEALLOCATE;
stringCompressor->DecodeString(serverDate, 128, &inBitStream);
// 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 = new AutopatcherClientCallback;
strcpy(transferCallback->applicationDirectory, applicationDirectory);
transferCallback->onFileCallback=cb;
transferCallback->client=this;
setId = fileListTransfer->SetupReceive(transferCallback, true, packet->playerId);
// 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);
stringCompressor->EncodeString(applicationName, 512, &outBitStream);
missingOrChanged.Serialize(&outBitStream);
rakPeer->Send(&outBitStream, priority, RELIABLE_ORDERED, orderingChannel, packet->playerId, false);
return RR_STOP_PROCESSING_AND_DEALLOCATE; // Absorb this message
}
void AutopatcherClient::OnDeletionList(RakPeerInterface *peer, Packet *packet)
{
if (packet->playerId!=serverId)
return;
RakNet::BitStream inBitStream(packet->data, packet->length, false);
RakNet::BitStream outBitStream;
inBitStream.IgnoreBits(8);
FileList fileList;
if (fileList.Deserialize(&inBitStream)==false)
return;
fileList.DeleteFiles(applicationDirectory);
}
PluginReceiveResult AutopatcherClient::OnAutopatcherFinished(RakPeerInterface *peer, Packet *packet)
{
fileListTransfer->RemoveReceiver(serverId);
// If redownload list, process it
if (redownloadList.fileList.Size())
{
RakNet::BitStream outBitStream;
AutopatcherClientCallback *transferCallback;
transferCallback = new AutopatcherClientCallback;
strcpy(transferCallback->applicationDirectory, applicationDirectory);
transferCallback->onFileCallback=cb;
transferCallback->client=this;
setId = fileListTransfer->SetupReceive(transferCallback, true, packet->playerId);
// 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);
stringCompressor->EncodeString(applicationName, 512, &outBitStream);
redownloadList.Serialize(&outBitStream);
rakPeer->Send(&outBitStream, priority, RELIABLE_ORDERED, orderingChannel, packet->playerId, false);
redownloadList.Clear();
return RR_STOP_PROCESSING_AND_DEALLOCATE; // Absorb
}
else if (copyAndRestartList.fileList.Size())
{
packet->data[0]=ID_AUTOPATCHER_RESTART_APPLICATION;
FILE *fp;
fp = fopen(copyOnRestartOut, "wt");
assert(fp);
if (fp)
{
fprintf(fp, "#Sleep 1000\n");
unsigned i;
for (i=0; i < copyAndRestartList.fileList.Size(); i++)
{
fprintf(fp, "del \"%s%s\"\n", applicationDirectory, copyAndRestartList.fileList[i].filename);
fprintf(fp, "rename \"%s%s%s\" \"%s\"\n", applicationDirectory, copyAndRestartList.fileList[i].filename, COPY_ON_RESTART_EXTENSION, copyAndRestartList.fileList[i].filename);
}
fprintf(fp, "#CreateProcess \"%s\"\n", restartExe);
fprintf(fp, "#DeleteThisFile\n");
fclose(fp);
}
}
RakNet::BitStream inBitStream(packet->data, packet->length, false);
inBitStream.IgnoreBits(8);
stringCompressor->DecodeString(serverDate, 128, &inBitStream);
// Return to user
return RR_CONTINUE_PROCESSING;
}
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, 0, 0, 0, 0);
}
void AutopatcherClient::Redownload(const char *filePath)
{
redownloadList.AddFile(filePath, 0, 0, 0, 0);
}
#ifdef _MSC_VER
#pragma warning( pop )
#endif
syntax highlighted by Code2HTML, v. 0.9.1