/* * 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-2021, 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 #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) { return ApplyPatchBSDiffHelper(oldFilePath, newFileContents, newFileSize, patchContents, patchSize); } PatchContext AutopatcherClientCBInterface::ApplyPatchBSDiffHelper(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_); // #med - missing return value evaluation (void)fread(prePatchFile, prePatchLength, 1, fp); fclose(fp); bool result = ApplyPatch(prePatchFile, prePatchLength, newFileContents, newFileSize, patchContents, patchSize); rakFree_Ex(prePatchFile, _FILE_AND_LINE_); if (!result) 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)) { 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 (input->cbInterface != nullptr) 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); else { // #high - design issue: The patch algorithm is specified by the server application and differs between the MySQL and PostgreSQL implementation where the PostgreSQL algorithm adds different patch algorithms // the client then needs to pass in the proper callback interface for the patch algorithm to match the server version - this should be fixed in a way that the client can still specify different // patch handling, but the default won't rely on the corresponding callback being passed on with the PatchApplication()-call - alongside the file callback handler and the patch callback handler should // really be separated from one another // #med - the patchalgorithm check actually belongs into AutopatcherClientCBInterface::ApplyPatchBase()... if (input->onFileStruct.context.flnc_extraData2 == 0) result = AutopatcherClientCBInterface::ApplyPatchBSDiffHelper(fullPathToDir, &input->postPatchFile, &input->postPatchLength, (char*)input->onFileStruct.fileData + HASH_LENGTH * hashMultiplier, input->onFileStruct.byteLengthOfThisFile - HASH_LENGTH * hashMultiplier); else // #med - consider adding a dedicated error code for this case as in PC_ERROR_UNSUPPORTED_PATCH_ALOGIRTHM result = PC_ERROR_PATCH_APPLICATION_FAILURE; } 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)) { 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 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(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 if (onFileCallback != nullptr) 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; if (onFileCallback != nullptr) onFileCallback->OnFile(&threadInfo->onFileStruct); threadInfo->onFileStruct.fileData=0; } } break; case PC_ERROR_FILE_WRITE_FAILURE: { if (threadInfo->onFileStruct.context.op==PC_WRITE_FILE) { if (onFileCallback != nullptr) onFileCallback->OnFile(&threadInfo->onFileStruct); } else { rakFree_Ex(threadInfo->onFileStruct.fileData, _FILE_AND_LINE_ ); threadInfo->onFileStruct.fileData=threadInfo->postPatchFile; threadInfo->onFileStruct.byteLengthOfThisFile=threadInfo->postPatchLength; if (onFileCallback != nullptr) onFileCallback->OnFile(&threadInfo->onFileStruct); threadInfo->onFileStruct.fileData=0; } } break; case PC_ERROR_PATCH_TARGET_MISSING: { if (onFileCallback != nullptr) onFileCallback->OnFile(&threadInfo->onFileStruct); client->Redownload(threadInfo->onFileStruct.fileName); } break; case PC_ERROR_PATCH_APPLICATION_FAILURE: { // Failure - signal class and download this file. if (onFileCallback != nullptr) onFileCallback->OnFile(&threadInfo->onFileStruct); client->Redownload(threadInfo->onFileStruct.fileName); } break; case PC_ERROR_PATCH_RESULT_CHECKSUM_FAILURE: { // Failure - signal class and download this file. if (onFileCallback != nullptr) onFileCallback->OnFile(&threadInfo->onFileStruct); client->Redownload(threadInfo->onFileStruct.fileName); } break; default: { if (threadInfo->onFileStruct.context.op==PC_WRITE_FILE) { if (onFileCallback != nullptr) onFileCallback->OnFile(&threadInfo->onFileStruct); } else { rakFree_Ex(threadInfo->onFileStruct.fileData, _FILE_AND_LINE_ ); threadInfo->onFileStruct.fileData=threadInfo->postPatchFile; if (onFileCallback != nullptr) 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 == nullptr) || (!onFileCallback->Update())) canDeleteUser=true; if ( downloadComplete && canDeleteUser && !threadPool.IsWorking()) { // 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 == nullptr) || (!onFileCallback->OnDownloadComplete(dcs))) { canDeleteUser=true; } return true; }; virtual void OnDereference(void) { if (onFileCallback != nullptr) onFileCallback->OnDereference(); StopThreads(); } virtual bool OnFile(OnFileStruct *onFileStruct) { AutopatcherClientThreadInfo *inStruct = SLNet::OP_NEW( _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; if (onFileCallback != nullptr) 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 ((onFileCallback != nullptr) && 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(-1); applicationDirectory[0] = '\0'; fileListTransfer = nullptr; priority = HIGH_PRIORITY; orderingChannel = '\0'; serverDate = 0.0; userCB = nullptr; 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); } // #med - consider nullptr-handling for passed in const char* 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( _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)) 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( _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)) 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)); }