This commit is contained in:
2025-11-24 14:19:51 +05:30
commit f5c1412b28
6734 changed files with 1527575 additions and 0 deletions

View File

@ -0,0 +1,479 @@
/*-
* Parts of this code are copyright 2003-2005 Colin Percival
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted providing that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This file was taken from RakNet 4.082.
* Please see licenses/RakNet license.txt for the underlying license and related copyright.
*
* Modified work: Copyright (c) 2016-2020, 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.
* Alternatively you are permitted to license the modifications under the Simplified BSD License.
*/
#if 0
__FBSDID("$FreeBSD: src/usr.bin/bsdiff/bspatch/bspatch.c,v 1.1 2005/08/06 01:59:06 cperciva Exp $");
#endif
#include "MemoryCompressor.h"
#include <bzlib.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifndef _WIN32
// KevinJ - Windows compatibility
#include <err.h>
#include <unistd.h>
#else
typedef int ssize_t;
#include <wchar.h>
#include <io.h>
#include "slikenet/linux_adapter.h"
#include "slikenet/osx_adapter.h"
#define fseeko fseek
static void err(int i, ...)
{
exit(i);
}
static void errx(int i, ...)
{
exit(i);
}
#endif
#include <fcntl.h>
#ifndef _O_BINARY
#define _O_BINARY 0
#endif
#ifndef O_BINARY
#define O_BINARY _O_BINARY
#endif
static off_t offtin(unsigned char *buf)
{
off_t y;
y=buf[7]&0x7F;
y=y*256;y+=buf[6];
y=y*256;y+=buf[5];
y=y*256;y+=buf[4];
y=y*256;y+=buf[3];
y=y*256;y+=buf[2];
y=y*256;y+=buf[1];
y=y*256;y+=buf[0];
if(buf[7]&0x80) y=-y;
return y;
}
// This function modifies the main() function included in bspatch.c of bsdiff-4.3 found at http://www.daemonology.net/bsdiff/
// It is changed to be a standalone function, to work entirely in memory, and to use my class MemoryDecompressor as an interface to BZip
// Up to the caller to deallocate new
bool ApplyPatch(char *old, unsigned int oldsize, char **_new, unsigned int *newsize, char *patch, unsigned int patchsize )
{
// FILE * f, * cpf, * dpf, * epf;
// BZFILE * cpfbz2, * dpfbz2, * epfbz2;
// int cbz2err, dbz2err, ebz2err;
// int fd;
// ssize_t oldsize,newsize;
ssize_t bzctrllen,bzdatalen;
//unsigned char header[32];
unsigned char buf[8];
// unsigned char *old, *_new;
off_t oldpos,newpos;
off_t ctrl[3];
// off_t lenread;
off_t i;
MemoryDecompressor decompress;
unsigned int coff, doff, eoff;
// if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]);
/* Open patch file */
// if (fopen_s(&f, argv[3], "rb") != 0)
// err(1, "fopen(%s)", argv[3]);
/*
File format:
0 8 "BSDIFF40"
8 8 X
16 8 Y
24 8 sizeof(newfile)
32 X bzip2(control block)
32+X Y bzip2(diff block)
32+X+Y ??? bzip2(extra block)
with control block a set of triples (x,y,z) meaning "add x bytes
from oldfile to x bytes from the diff block; copy y bytes from the
extra block; seek forwards in oldfile by z bytes".
*/
/* Read header */
// if (fread(header, 1, 32, f) < 32) {
// if (feof(f))
// errx(1, "Corrupt patch\n",0);
// err(1, "fread(%s)", argv[3]);
// }
// memcpy(header, patch, 32);
/* Check for appropriate magic */
if (memcmp(patch, "BSDIFF40", 8) != 0)
// errx(1, "Corrupt patch\n",0);
return false;
/* Read lengths from header */
bzctrllen=offtin((unsigned char*)patch+8);
bzdatalen=offtin((unsigned char*)patch+16);
*newsize=offtin((unsigned char*)patch+24);
if((bzctrllen<0) || (bzdatalen<0) || (*newsize<0))
// errx(1,"Corrupt patch\n",0);
return false;
/* Close patch file and re-open it via libbzip2 at the right places */
// if (fclose(f))
// err(1, "fclose(%s)", argv[3]);
// if (fopen_s(&cpf, argv[3], "rb") != 0)
// err(1, "fopen(%s)", argv[3]);
// if (fseeko(cpf, 32, SEEK_SET))
// err(1, "fseeko(%s, %lld)", argv[3],
// (long long)32);
// if ((cpfbz2 = BZ2_bzReadOpen(&cbz2err, cpf, 0, 0, nullptr, 0)) == nullptr)
// errx(1, "BZ2_bzReadOpen, bz2err = %d", cbz2err);
// if (fopen_s(&dpf, argv[3], "rb") != 0)
// err(1, "fopen(%s)", argv[3]);
// if (fseeko(dpf, 32 + bzctrllen, SEEK_SET))
// err(1, "fseeko(%s, %lld)", argv[3],
// (long long)(32 + bzctrllen));
// if ((dpfbz2 = BZ2_bzReadOpen(&dbz2err, dpf, 0, 0, nullptr, 0)) == nullptr)
// errx(1, "BZ2_bzReadOpen, bz2err = %d", dbz2err);
// if (fopen_s(&epf, argv[3], "rb") != 0)
// err(1, "fopen(%s)", argv[3]);
// if (fseeko(epf, 32 + bzctrllen + bzdatalen, SEEK_SET))
// err(1, "fseeko(%s, %lld)", argv[3],
// (long long)(32 + bzctrllen + bzdatalen));
// if ((epfbz2 = BZ2_bzReadOpen(&ebz2err, epf, 0, 0, nullptr, 0)) == nullptr)
// errx(1, "BZ2_bzReadOpen, bz2err = %d", ebz2err);
// decompress.Decompress((char*)patch+32, patchsize-32, true);
coff=0;
if (decompress.Decompress((char*)patch+32, bzctrllen, false)==false)
return false;
doff=decompress.GetTotalOutputSize();
if (decompress.Decompress((char*)patch+32+bzctrllen, bzdatalen, false)==false)
return false;
eoff=decompress.GetTotalOutputSize();
if (decompress.Decompress((char*)patch+32+bzctrllen+bzdatalen, patchsize-(32+bzctrllen+bzdatalen), true)==false)
return false;
// if(((fd=_open(argv[1],O_RDONLY | _O_BINARY,0))<0) ||
// ((oldsize=_lseek(fd,0,SEEK_END))==-1) ||
// ((old=(unsigned char*)malloc(oldsize+1))==nullptr) ||
// (_lseek(fd,0,SEEK_SET)!=0) ||
// (_read(fd,old,oldsize)!=oldsize) ||
// (_close(fd)==-1)) err(1,"%s",argv[1]);
// if((_new=(unsigned char*)malloc(newsize+1))==nullptr) err(1,nullptr);
*_new = new char[*newsize+1];
oldpos=0;newpos=0;
while(newpos<(off_t)*newsize) {
/* Read control data */
for(i=0;i<=2;i++) {
// lenread = BZ2_bzRead(&cbz2err, cpfbz2, buf, 8);
// if ((lenread < 8) || ((cbz2err != BZ_OK) &&
// (cbz2err != BZ_STREAM_END)))
// errx(1, "Corrupt patch\n");
memcpy(buf, decompress.GetOutput()+coff, 8);
coff+=8;
ctrl[i]=offtin(buf);
};
/* Sanity-check */
if(newpos+ctrl[0]>(off_t)*newsize)
{
delete [] (*_new);
return false;
}
/* Read diff string */
//lenread = BZ2_bzRead(&dbz2err, dpfbz2, _new + newpos, ctrl[0]);
//if ((lenread < ctrl[0]) ||
// ((dbz2err != BZ_OK) && (dbz2err != BZ_STREAM_END)))
// errx(1, "Corrupt patch\n");
memcpy(*_new + newpos, decompress.GetOutput()+doff, ctrl[0]);
doff+=ctrl[0];
/* Add old data to diff string */
for(i=0;i<ctrl[0];i++)
if((oldpos+i>=0) && (oldpos+i<(off_t)oldsize))
(*_new)[newpos+i]+=old[oldpos+i];
/* Adjust pointers */
newpos+=ctrl[0];
oldpos+=ctrl[0];
/* Sanity-check */
if(newpos+ctrl[1]>(off_t)*newsize)
{
delete [] (*_new);
return false;
}
/* Read extra string */
//lenread = BZ2_bzRead(&ebz2err, epfbz2, _new + newpos, ctrl[1]);
//if ((lenread < ctrl[1]) ||
// ((ebz2err != BZ_OK) && (ebz2err != BZ_STREAM_END)))
// errx(1, "Corrupt patch\n");
memcpy(*_new + newpos, decompress.GetOutput()+eoff, ctrl[1]);
eoff+=ctrl[1];
/* Adjust pointers */
newpos+=ctrl[1];
oldpos+=ctrl[2];
};
/* Clean up the bzip2 reads */
// BZ2_bzReadClose(&cbz2err, cpfbz2);
// BZ2_bzReadClose(&dbz2err, dpfbz2);
// BZ2_bzReadClose(&ebz2err, epfbz2);
// if (fclose(cpf) || fclose(dpf) || fclose(epf))
// err(1, "fclose(%s)", argv[3]);
/* Write the new file */
// if(((fd=_open(argv[2],O_CREAT|O_TRUNC|O_WRONLY,0666))<0) ||
// (_write(fd,_new,newsize)!=newsize) || (_close(fd)==-1))
// err(1,"%s",argv[2]);
// free(_new);
// free(old);
return true;
}
int TestPatchInMemory(int argc,char *argv[])
{
FILE *patchFile, *newFile, *oldFile;
char *patch,*_new,*old;
unsigned patchSize, oldSize;
unsigned int newSize;
int res;
if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]);
fopen_s(&patchFile, argv[3], "rb");
fopen_s(&newFile, argv[2], "wb");
fopen_s(&oldFile, argv[1], "rb");
fseeko(patchFile, 0, SEEK_END);
fseeko(oldFile, 0, SEEK_END);
patchSize=ftell(patchFile);
oldSize=ftell(oldFile);
fseeko(patchFile, 0, SEEK_SET);
fseeko(oldFile, 0, SEEK_SET);
patch = new char [patchSize];
old = new char [oldSize];
fread(patch, patchSize, 1, patchFile);
fread(old, oldSize, 1, oldFile);
// #low Should not allocate memory in ApplyPatch and then free it in the caller again
// also return value looks inconsistent
res = (ApplyPatch( old, oldSize, &_new, &newSize,patch, patchSize) ? 1 : 0);
delete[] old;
delete[] patch;
fwrite(_new, newSize, 1, newFile);
if (res == 1) {
// if res == 0, _new was already cleared in ApplyPatch
delete[] _new;
}
fclose(patchFile);
fclose(newFile);
fclose(oldFile);
return res;
}
int PATCH_main(int argc,char * argv[])
{
FILE * f, * cpf, * dpf, * epf;
BZFILE * cpfbz2, * dpfbz2, * epfbz2;
int cbz2err, dbz2err, ebz2err;
int fd;
ssize_t oldsize,newsize;
ssize_t bzctrllen,bzdatalen;
unsigned char header[32],buf[8];
unsigned char *old, *_new;
off_t oldpos,newpos;
off_t ctrl[3];
off_t lenread;
off_t i;
unsigned bytesRead=0;
if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]);
/* Open patch file */
if (fopen_s(&f, argv[3], "rb") != 0)
err(1, "fopen(%s)", argv[3]);
/*
File format:
0 8 "BSDIFF40"
8 8 X
16 8 Y
24 8 sizeof(newfile)
32 X bzip2(control block)
32+X Y bzip2(diff block)
32+X+Y ??? bzip2(extra block)
with control block a set of triples (x,y,z) meaning "add x bytes
from oldfile to x bytes from the diff block; copy y bytes from the
extra block; seek forwards in oldfile by z bytes".
*/
/* Read header */
if (fread(header, 1, 32, f) < 32) {
if (feof(f))
errx(1, "Corrupt patch\n");
err(1, "fread(%s)", argv[3]);
}
/* Check for appropriate magic */
if (memcmp(header, "BSDIFF40", 8) != 0)
errx(1, "Corrupt patch\n");
/* Read lengths from header */
bzctrllen=offtin(header+8);
bzdatalen=offtin(header+16);
newsize=offtin(header+24);
if((bzctrllen<0) || (bzdatalen<0) || (newsize<0))
errx(1,"Corrupt patch\n");
/* Close patch file and re-open it via libbzip2 at the right places */
if (fclose(f))
err(1, "fclose(%s)", argv[3]);
if (fopen_s(&cpf, argv[3], "rb") != 0)
err(1, "fopen(%s)", argv[3]);
if (fseeko(cpf, 32, SEEK_SET))
err(1, "fseeko(%s, %lld)", argv[3],
(long long)32);
if ((cpfbz2 = BZ2_bzReadOpen(&cbz2err, cpf, 0, 0, nullptr, 0)) == nullptr)
errx(1, "BZ2_bzReadOpen, bz2err = %d", cbz2err);
if (fopen_s(&dpf, argv[3], "rb") != 0)
err(1, "fopen(%s)", argv[3]);
if (fseeko(dpf, 32 + bzctrllen, SEEK_SET))
err(1, "fseeko(%s, %lld)", argv[3],
(long long)(32 + bzctrllen));
if ((dpfbz2 = BZ2_bzReadOpen(&dbz2err, dpf, 0, 0, nullptr, 0)) == nullptr)
errx(1, "BZ2_bzReadOpen, bz2err = %d", dbz2err);
if (fopen_s(&epf, argv[3], "rb") != 0)
err(1, "fopen(%s)", argv[3]);
if (fseeko(epf, 32 + bzctrllen + bzdatalen, SEEK_SET))
err(1, "fseeko(%s, %lld)", argv[3],
(long long)(32 + bzctrllen + bzdatalen));
if ((epfbz2 = BZ2_bzReadOpen(&ebz2err, epf, 0, 0, nullptr, 0)) == nullptr)
errx(1, "BZ2_bzReadOpen, bz2err = %d", ebz2err);
if(((fd=_open(argv[1],O_RDONLY|O_BINARY,0))<0) ||
((oldsize=_lseek(fd,0,SEEK_END))==-1) ||
((old=(unsigned char*)malloc(oldsize+1))== nullptr) ||
(_lseek(fd,0,SEEK_SET)!=0) ||
(_read(fd,old,oldsize)!=oldsize) ||
(_close(fd)==-1)) err(1,"%s",argv[1]);
if((_new=(unsigned char*)malloc(newsize+1))== nullptr) err(1, nullptr);
oldpos=0;newpos=0;
while(newpos<newsize) {
/* Read control data */
for(i=0;i<=2;i++) {
lenread = BZ2_bzRead(&cbz2err, cpfbz2, buf, 8);
if ((lenread < 8) || ((cbz2err != BZ_OK) &&
(cbz2err != BZ_STREAM_END)))
errx(1, "Corrupt patch\n");
bytesRead+=8;
//printf("cbz2err cpfbz2 %i %i\n", 8, bytesRead);
ctrl[i]=offtin(buf);
};
/* Sanity-check */
if(newpos+ctrl[0]>newsize)
errx(1,"Corrupt patch\n");
/* Read diff string */
lenread = BZ2_bzRead(&dbz2err, dpfbz2, _new + newpos, ctrl[0]);
bytesRead+=8;
// printf("dbz2err dpfbz2 %i %i\n", ctrl[0], bytesRead);
if ((lenread < ctrl[0]) ||
((dbz2err != BZ_OK) && (dbz2err != BZ_STREAM_END)))
errx(1, "Corrupt patch\n");
/* Add old data to diff string */
for(i=0;i<ctrl[0];i++)
if((oldpos+i>=0) && (oldpos+i<oldsize))
_new[newpos+i]+=old[oldpos+i];
/* Adjust pointers */
newpos+=ctrl[0];
oldpos+=ctrl[0];
/* Sanity-check */
if(newpos+ctrl[1]>newsize)
errx(1,"Corrupt patch\n");
/* Read extra string */
lenread = BZ2_bzRead(&ebz2err, epfbz2, _new + newpos, ctrl[1]);
bytesRead+=8;
// printf("ebz2err epfbz2 %i %i\n", ctrl[1], bytesRead);
if ((lenread < ctrl[1]) ||
((ebz2err != BZ_OK) && (ebz2err != BZ_STREAM_END)))
errx(1, "Corrupt patch\n");
/* Adjust pointers */
newpos+=ctrl[1];
oldpos+=ctrl[2];
};
/* Clean up the bzip2 reads */
BZ2_bzReadClose(&cbz2err, cpfbz2);
BZ2_bzReadClose(&dbz2err, dpfbz2);
BZ2_bzReadClose(&ebz2err, epfbz2);
if (fclose(cpf) || fclose(dpf) || fclose(epf))
err(1, "fclose(%s)", argv[3]);
/* Write the new file */
if(((fd=_open(argv[2],O_CREAT|O_TRUNC|O_WRONLY|O_BINARY,0666))<0) ||
(_write(fd,_new,newsize)!=newsize) || (_close(fd)==-1))
err(1,"%s",argv[2]);
free(_new);
free(old);
return 0;
}

View File

@ -0,0 +1,8 @@
/*
* This file was taken from RakNet 4.082 without any modifications.
* Please see licenses/RakNet license.txt for the underlying license and related copyright.
*/
/// Apply \a patch to \a old. Will return the new file in \a _new which is allocated for you.
bool ApplyPatch( char *old, unsigned int oldsize, char **_new, unsigned int *newsize, char *patch, unsigned int patchsize );

View File

@ -0,0 +1,683 @@
/*
* 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));
}

View File

@ -0,0 +1,131 @@
/*
* 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.
*/
/// \file
/// \brief Client plugin for the autopatcher
#ifndef __AUTOPATCHER_CLIENT_H
#define __AUTOPATCHER_CLIENT_H
#include "slikenet/types.h"
#include "slikenet/Export.h"
#include "slikenet/PluginInterface2.h"
#include "slikenet/PacketPriority.h"
#include "slikenet/FileList.h"
#include "slikenet/SimpleMutex.h"
#include "slikenet/FileListTransferCBInterface.h"
#include "slikenet/AutopatcherPatchContext.h"
namespace SLNet
{
class RakPeerInterface;
struct Packet;
class FileListTransfer;
class RAK_DLL_EXPORT AutopatcherClientCBInterface : public FileListTransferCBInterface
{
public:
virtual PatchContext ApplyPatchBSDiff(const char *oldFilePath, char **newFileContents, unsigned int *newFileSize, char *patchContents, unsigned int patchSize);
virtual PatchContext ApplyPatchBase(const char *oldFilePath, char **newFileContents, unsigned int *newFileSize, char *patchContents, unsigned int patchSize, uint32_t patchAlgorithm);
};
/// \ingroup Autopatcher
class RAK_DLL_EXPORT AutopatcherClient : public PluginInterface2
{
public:
// Constructor
AutopatcherClient();
// Destructor
~AutopatcherClient();
/// Are we in the middle of patching?
/// \return True if yes, false otherwise.
bool IsPatching(void) const;
/// What parameters to use for the RakPeerInterface::Send() call when uploading files.
/// \param[in] _priority See RakPeerInterface::Send()
/// \param[in] _orderingChannel See RakPeerInterface::Send()
void SetUploadSendParameters(PacketPriority _priority, char _orderingChannel);
/// This plugin has a dependency on the FileListTransfer plugin, which it uses to actually send the files.
/// So you need an instance of that plugin registered with RakPeerInterface, and a pointer to that interface should be passed here.
/// \param[in] flt A pointer to a registered instance of FileListTransfer
void SetFileListTransferPlugin(FileListTransfer *flt);
/// Patches a certain directory associated with a named application to match the same named application on the patch server
/// \param[in] _applicationName The name of the application
/// \param[in] _applicationDirectory The directory to write the output to.
/// \param[in] lastUpdateDate Returned by time() (seconds since EPOCH) on the server, as well as returned in GetServerDate() after you call PatchApplication successfully. When distributing your application, set to the server time() when the distribution was created. You can pass 0 as well, however this will do a full scan of the entire game so is very slow. See 'Optimizing for large games' in the manual under Help/autopatcher.html
/// \param[in] host The address of the remote system to send the message to.
/// \param[in] onFileCallback Callback to call per-file (optional). When fileIndex+1==setCount in the callback then the download is done
/// \param[in] _restartOutputFilename If it is necessary to restart this application, where to write the restart data to. You can include a path in this filename.
/// \param[in] pathToRestartExe What exe to launch from the AutopatcherClientRestarter . argv[0] will work to relaunch this application.
/// \return true on success, false on failure. On failure, ID_AUTOPATCHER_REPOSITORY_FATAL_ERROR is returned, with the error message written using the stringCompressor class starting after the message id.
bool PatchApplication(const char *_applicationName, const char *_applicationDirectory, double lastUpdateDate, SystemAddress host, AutopatcherClientCBInterface *onFileCallback, const char *restartOutputFilename, const char *pathToRestartExe);
/// After getting ID_AUTOPATCHER_FINISHED or ID_AUTOPATCHER_RESTART_APPLICATION, the server date will be internally recorded. You can send this to PatchApplication::lastUpdateDate to only check for files newer than that date.
double GetServerDate(void) const;
/// Stop processing the current download
/// \note The files in progress will continue to transfer. The only way to stop it is to call CloseConnection(true), then reconnect to the server once you get ID_DISCONNECTION_NOTIFICATION
void CancelDownload(void);
/// Free internal memory.
void Clear(void);
/// \internal For plugin handling
virtual void Update(void);
/// \internal For plugin handling
virtual PluginReceiveResult OnReceive(Packet *packet);
void OnClosedConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason );
/// \internal For plugin handling
virtual void OnShutdown(void);
void OnThreadCompletion(void);
protected:
PluginReceiveResult OnCreationList(Packet *packet);
void OnDeletionList(Packet *packet);
PluginReceiveResult OnDownloadFinishedInternal(Packet *packet);
PluginReceiveResult OnDownloadFinished(Packet *packet);
friend class AutopatcherClientCallback;
void CopyAndRestart(const char *filePath);
void Redownload(const char *filePath);
char applicationDirectory[512];
char applicationName[512];
char copyOnRestartOut[512];
char restartExe[512];
double serverDate;
FileListTransfer *fileListTransfer;
PacketPriority priority;
SystemAddress serverId;
SystemIndex serverIdIndex;
char orderingChannel;
unsigned short setId;
AutopatcherClientCBInterface *userCB;
bool patchComplete;
FileList redownloadList, copyAndRestartList;
bool processThreadCompletion;
SimpleMutex processThreadCompletionMutex;
};
} // namespace SLNet
#endif

View File

@ -0,0 +1,844 @@
/*
* 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) 2016-2018, 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 "slikenet/string.h"
#include "AutopatcherMySQLRepository.h"
#include "slikenet/AutopatcherPatchContext.h"
#include "slikenet/FileList.h"
#include "slikenet/assert.h"
#include "slikenet/DS_List.h"
// ntohl
#ifdef _WIN32
#include <Winsock2.h>
#else
#include <netinet/in.h>
#endif
// If you get fatal error C1083: Cannot open include file: 'mysql.h' then you need to install MySQL. See readme.txt in this sample directory.
#include "mysql.h"
#include "CreatePatch.h"
#include "slikenet/AutopatcherPatchContext.h"
// #include "slikenet/DR_SHA1.h"
#include <stdlib.h>
#include "slikenet/LinuxStrings.h"
#include "slikenet/linux_adapter.h"
#include "slikenet/osx_adapter.h"
using namespace SLNet;
static const unsigned HASH_LENGTH=sizeof(unsigned int);
struct FileInfo
{
SLNet::RakString filename;
char contentHash [HASH_LENGTH];
bool createFile;
};
// alloca
#ifdef _COMPATIBILITY_1
#elif defined(_WIN32)
#include <malloc.h>
#else
//#include <stdlib.h>
#endif
#define PQEXECPARAM_FORMAT_TEXT 0
#define PQEXECPARAM_FORMAT_BINARY 1
AutopatcherMySQLRepository::AutopatcherMySQLRepository()
{
filePartConnection=0;
}
AutopatcherMySQLRepository::~AutopatcherMySQLRepository()
{
if (filePartConnection)
mysql_close(filePartConnection);
}
bool AutopatcherMySQLRepository::CreateAutopatcherTables(void)
{
if (!IsConnected())
return false;
//sqlCommandMutex.Lock();
ExecuteBlockingCommand("BEGIN;");
if (!ExecuteBlockingCommand(
"CREATE TABLE Applications ("
"applicationID INT AUTO_INCREMENT,"
"applicationName VARCHAR(255) NOT NULL UNIQUE,"
"changeSetID integer NOT NULL DEFAULT 0,"
"userName TEXT NOT NULL,"
"PRIMARY KEY (applicationID));"
)) {Rollback(); return false;}
if (!ExecuteBlockingCommand(
"CREATE TABLE FileVersionHistory ("
"fileID INT AUTO_INCREMENT,"
"applicationID INT NOT NULL, "
"filename VARCHAR(255) NOT NULL,"
"fileLength INT,"
"content LONGBLOB,"
"contentHash TINYBLOB,"
"patch LONGBLOB,"
"createFile TINYINT NOT NULL,"
"modificationDate double precision DEFAULT (EXTRACT(EPOCH FROM now())),"
"lastSentDate double precision,"
"timesSent INT NOT NULL DEFAULT 0,"
"changeSetID INT NOT NULL,"
"userName TEXT NOT NULL,"
"PRIMARY KEY (fileID)); "
)) {Rollback(); return false;}
if (!ExecuteBlockingCommand(
"CREATE INDEX FV_appID on FileVersionHistory(applicationID);"
)) {Rollback(); return false;}
if (!ExecuteBlockingCommand(
"CREATE INDEX FV_fname on FileVersionHistory(filename);"
)) {Rollback(); return false;}
if (!ExecuteBlockingCommand(
"CREATE VIEW AutoPatcherView AS SELECT "
"FileVersionHistory.applicationid,"
"Applications.applicationName,"
"FileVersionHistory.fileID,"
"FileVersionHistory.fileName,"
"FileVersionHistory.createFile,"
"FileVersionHistory.fileLength,"
"FileVersionHistory.changeSetID,"
"FileVersionHistory.lastSentDate,"
"FileVersionHistory.modificationDate,"
"FileVersionHistory.timesSent "
"FROM (FileVersionHistory JOIN Applications ON "
"( FileVersionHistory.applicationID = Applications.applicationID )) "
"ORDER BY Applications.applicationID ASC, FileVersionHistory.fileID ASC;"
)) {Rollback(); return false;}
bool b = ExecuteBlockingCommand("COMMIT;");
//sqlCommandMutex.Unlock();
return b;
}
bool AutopatcherMySQLRepository::DestroyAutopatcherTables(void)
{
if (!IsConnected())
return false;
//sqlCommandMutex.Lock();
ExecuteBlockingCommand("DROP INDEX FV_appID;");
ExecuteBlockingCommand("DROP INDEX FV_fname;");
ExecuteBlockingCommand("DROP TABLE Applications CASCADE;");
ExecuteBlockingCommand("DROP TABLE FileVersionHistory CASCADE;");
bool b = ExecuteBlockingCommand("DROP VIEW AutoPatcherView;");
//sqlCommandMutex.Unlock();
return b;
}
bool AutopatcherMySQLRepository::AddApplication(const char *applicationName, const char *userName)
{
// mysql_real_escape_string
char query[512];
sprintf_s(query, "INSERT INTO Applications (applicationName, userName) VALUES ('%s', '%s');", GetEscapedString(applicationName).C_String(), GetEscapedString(userName).C_String());
//sqlCommandMutex.Lock();
bool b = ExecuteBlockingCommand(query);
//sqlCommandMutex.Unlock();
return b;
}
bool AutopatcherMySQLRepository::RemoveApplication(const char *applicationName)
{
char query[512];
sprintf_s(query, "DELETE FROM Applications WHERE applicationName='%s';", GetEscapedString(applicationName).C_String());
//sqlCommandMutex.Lock();
bool b = ExecuteBlockingCommand(query);
//sqlCommandMutex.Unlock();
return b;
}
bool AutopatcherMySQLRepository::GetChangelistSinceDate(const char *applicationName, FileList *addedOrModifiedFilesWithHashData, FileList *deletedFiles, double sinceDate)
{
char query[512];
SLNet::RakString escapedApplicationName = GetEscapedString(applicationName);
sprintf_s(query, "SELECT applicationID FROM Applications WHERE applicationName='%s';", escapedApplicationName.C_String());
int applicationID;
//sqlCommandMutex.Lock();
if (!ExecuteQueryReadInt(query, &applicationID))
{
// This message covers the lost connection to the SQL server
//sqlCommandMutex.Unlock();
//sprintf_s(lastError,"ERROR: %s not found in UpdateApplicationFiles\n",escapedApplicationName.C_String());
return false;
}
//sqlCommandMutex.Unlock();
if (sinceDate!=0)
sprintf_s(query,
"SELECT filename, fileLength, contentHash, createFile, fileId FROM FileVersionHistory "
"JOIN (SELECT max(fileId) maxId FROM FileVersionHistory WHERE applicationId=%i AND modificationDate > %f GROUP BY fileName) MaxId "
"ON FileVersionHistory.fileId = MaxId.maxId "
"ORDER BY filename DESC;", applicationID,sinceDate);
else
sprintf_s(query,
"SELECT filename, fileLength, contentHash, createFile, fileId FROM FileVersionHistory "
"JOIN (SELECT max(fileId) maxId FROM FileVersionHistory WHERE applicationId=%i GROUP BY fileName) MaxId "
"ON FileVersionHistory.fileId = MaxId.maxId "
"ORDER BY filename DESC;", applicationID);
MYSQL_RES * result = 0;
//sqlCommandMutex.Lock();
if (!ExecuteBlockingCommand (query, &result))
{
//sqlCommandMutex.Unlock();
return false;
}
//sqlCommandMutex.Unlock();
MYSQL_ROW row;
while ((row = mysql_fetch_row (result)) != 0)
{
const char * createFileResult = row [3];
const char * hardDriveFilename = row [0];
if (createFileResult[0]=='1')
{
const char * hardDriveHash = row [2];
int fileLength = atoi (row [1]);
addedOrModifiedFilesWithHashData->AddFile(hardDriveFilename, hardDriveFilename, hardDriveHash, HASH_LENGTH, fileLength, FileListNodeContext(), false);
}
else
{
deletedFiles->AddFile(hardDriveFilename,hardDriveFilename,0,0,0,FileListNodeContext(), false);
}
}
mysql_free_result (result);
return true;
}
int AutopatcherMySQLRepository::GetPatches(const char *applicationName, FileList *input, bool allowDownloadOfOriginalUnmodifiedFiles, FileList *patchList)
{
char query[512];
SLNet::RakString escapedApplicationName = GetEscapedString(applicationName);
sprintf_s(query, "SELECT applicationID FROM Applications WHERE applicationName='%s';", escapedApplicationName.C_String());
int applicationID;
//sqlCommandMutex.Lock();
if (!ExecuteQueryReadInt (query, &applicationID))
{
//sqlCommandMutex.Unlock();
sprintf_s(lastError,"ERROR: %s not found in GetPatches\n",applicationName);
return false;
}
//sqlCommandMutex.Unlock();
// Go through the input list.
for (unsigned inputIndex=0; inputIndex < input->fileList.Size(); inputIndex++)
{
const char * userHash=input->fileList[inputIndex].data;
const char * userFilename=input->fileList[inputIndex].filename;
char *fn = new char [(strlen(userFilename))*2+1];
mysql_real_escape_string(mySqlConnection, fn, userFilename, (unsigned long) strlen(userFilename));
if (userHash==0)
{
// If the user does not have a hash in the input list, get the contents of latest version of this named file and write it to the patch list
// sprintf_s(query, "SELECT content FROM FileVersionHistory "
// "JOIN (SELECT max(fileId) maxId FROM FileVersionHistory WHERE applicationId=%i AND filename='%s') MaxId "
// "ON FileVersionHistory.fileId = MaxId.maxId",
// applicationID, fn);
sprintf_s(query, "SELECT fileId, fileLength, changeSetID FROM FileVersionHistory "
"JOIN (SELECT max(fileId) maxId FROM FileVersionHistory WHERE applicationId=%i AND filename='%s') MaxId "
"ON FileVersionHistory.fileId = MaxId.maxId",
applicationID, fn);
MYSQL_RES * result = 0;
//sqlCommandMutex.Lock();
if (!ExecuteBlockingCommand (query, &result))
{
//sqlCommandMutex.Unlock();
delete [] fn;
return false;
}
//sqlCommandMutex.Unlock();
MYSQL_ROW row = mysql_fetch_row (result);
if (row != 0)
{
//const char * content = row [0];
//unsigned long contentLength=mysql_fetch_lengths (result) [0];
//patchList->AddFile(userFilename, content, contentLength, contentLength, FileListNodeContext(PC_WRITE_FILE,0));
const int fileId = atoi (row [0]);
const int fileLength = atoi (row [1]);
const int changeSetID = atoi (row [2]);
if (allowDownloadOfOriginalUnmodifiedFiles==false && changeSetID==0)
{
printf("Failure: allowDownloadOfOriginalUnmodifiedFiles==false for %s length %i\n", userFilename, fileLength);
mysql_free_result(result);
return false;
}
patchList->AddFile(userFilename,userFilename, 0, fileLength, fileLength, FileListNodeContext(PC_WRITE_FILE,fileId,0,0), true);
}
mysql_free_result(result);
}
else // Assuming the user does have a hash.
{
if (input->fileList[inputIndex].dataLengthBytes!=HASH_LENGTH)
{
delete [] fn;
return false;
}
// Get the hash and ID of the latest version of this file, by filename.
sprintf_s(query,
"SELECT contentHash, fileId, fileLength FROM FileVersionHistory "
"JOIN (SELECT max(fileId) maxId FROM FileVersionHistory WHERE applicationId=%i AND filename='%s') MaxId "
"ON FileVersionHistory.fileId = MaxId.maxId",
applicationID, fn);
MYSQL_RES * result = 0;
//sqlCommandMutex.Lock();
if (!ExecuteBlockingCommand (query, &result))
{
//sqlCommandMutex.Unlock();
delete [] fn;
return false;
}
//sqlCommandMutex.Unlock();
MYSQL_ROW row = mysql_fetch_row (result);
if (row != 0)
{
const char * contentHash = row [0];
const int fileId = atoi (row [1]);
const int fileLength = atoi (row [2]); // double check if this works
if (memcmp(contentHash, userHash, HASH_LENGTH)!=0)
{
char buf [2 * HASH_LENGTH + 1];
mysql_real_escape_string(mySqlConnection, buf, userHash, HASH_LENGTH);
sprintf_s(query, "SELECT patch FROM FileVersionHistory WHERE applicationId=%i AND filename='%s' AND contentHash='%s'; ", applicationID, fn, buf);
MYSQL_RES * patchResult = 0;
//sqlCommandMutex.Lock();
if (!ExecuteBlockingCommand (query, &patchResult))
{
//sqlCommandMutex.Unlock();
delete [] fn;
return false;
}
//sqlCommandMutex.Unlock();
row = mysql_fetch_row (patchResult);
if (row==0)
{
// If no patch found, then this is a non-release version, or a very old version we are no longer tracking.
// Get the contents of latest version of this named file by fileId and return it.
/*
sprintf_s(query, "SELECT content FROM FileVersionHistory WHERE fileId=%d;", fileId);
if (mysql_query (mySqlConnection, query) != 0)
{
delete [] fn;
strcpy (lastError, mysql_error (mySqlConnection));
return false;
}
MYSQL_RES * substrresult = mysql_store_result (mySqlConnection);
row = mysql_fetch_row (substrresult);
char * file = row [0];
unsigned long contentLength = mysql_fetch_lengths (substrresult) [0];
patchList->AddFile(userFilename, file, fileLength, contentLength, FileListNodeContext(PC_WRITE_FILE,0));
mysql_free_result(substrresult);
*/
patchList->AddFile(userFilename,userFilename, 0, fileLength, fileLength, FileListNodeContext(PC_WRITE_FILE,fileId,0,0), true);
}
else
{
// Otherwise, write the hash of the new version and then write the patch to get to that version.
//
char * patch = row [0];
unsigned long patchLength = mysql_fetch_lengths (patchResult) [0];
char *temp = new char [patchLength + HASH_LENGTH];
memcpy(temp, contentHash, HASH_LENGTH);
memcpy(temp+HASH_LENGTH, patch, patchLength);
patchList->AddFile(userFilename,userFilename, temp, HASH_LENGTH+patchLength, fileLength, FileListNodeContext(PC_HASH_1_WITH_PATCH,0,0,0) );
delete [] temp;
}
mysql_free_result(patchResult);
}
else
{
// else if the hash of this file matches what the user has, the user has the latest version. Done.
}
}
else
{
// else if there is no such file, skip this file.
}
mysql_free_result(result);
}
delete [] fn;
}
return true;
}
bool AutopatcherMySQLRepository::GetMostRecentChangelistWithPatches(SLNet::RakString &applicationName, FileList *patchedFiles, FileList *addedFiles, FileList *addedOrModifiedFileHashes, FileList *deletedFiles, double *priorRowPatchTime, double *mostRecentRowPatchTime)
{
// unused parameters
(void)applicationName;
(void)patchedFiles;
(void)addedFiles;
(void)addedOrModifiedFileHashes;
(void)deletedFiles;
(void)priorRowPatchTime;
(void)mostRecentRowPatchTime;
// Not yet implemented
return false;
}
bool AutopatcherMySQLRepository::UpdateApplicationFiles(const char *applicationName, const char *applicationDirectory, const char *userName, FileListProgress *cb)
{
MYSQL_STMT *stmt;
MYSQL_BIND bind[3];
char query[512];
FileList filesOnHarddrive;
filesOnHarddrive.AddCallback(cb);
int prepareResult;
my_bool falseVar=false;
SLNet::RakString escapedApplicationName = GetEscapedString(applicationName);
filesOnHarddrive.AddFilesFromDirectory(applicationDirectory,"", true, true, true, FileListNodeContext());
if (filesOnHarddrive.fileList.Size()==0)
{
sprintf_s(lastError,"ERROR: Can't find files at %s in UpdateApplicationFiles\n",applicationDirectory);
return false;
}
sprintf_s(query, "SELECT applicationID FROM Applications WHERE applicationName='%s';", escapedApplicationName.C_String());
int applicationID;
//sqlCommandMutex.Lock();
if (!ExecuteQueryReadInt(query, &applicationID))
{
//sqlCommandMutex.Unlock();
sprintf_s(lastError,"ERROR: %s not found in UpdateApplicationFiles\n",escapedApplicationName.C_String());
return false;
}
if (!ExecuteBlockingCommand("BEGIN;"))
{
//sqlCommandMutex.Unlock();
return false;
}
//sqlCommandMutex.Unlock();
sprintf_s(query, "UPDATE Applications SET changeSetId = changeSetId + 1 where applicationID=%i;", applicationID);
//sqlCommandMutex.Lock();
if (!ExecuteBlockingCommand(query))
{
Rollback ();
//sqlCommandMutex.Unlock();
return false;
}
//sqlCommandMutex.Unlock();
int changeSetId = 0;
sprintf_s(query, "SELECT changeSetId FROM Applications WHERE applicationID=%i;", applicationID);
//sqlCommandMutex.Lock();
if (!ExecuteQueryReadInt(query, &changeSetId))
{
Rollback ();
//sqlCommandMutex.Unlock();
return false;
}
//sqlCommandMutex.Unlock();
// +1 was added in the update
changeSetId--;
// Gets all newest files
sprintf_s(query, "SELECT filename, contentHash, createFile FROM FileVersionHistory "
"JOIN (SELECT max(fileId) maxId FROM FileVersionHistory WHERE applicationId=%i GROUP BY fileName) MaxId "
"ON FileVersionHistory.fileId = MaxId.maxId "
"ORDER BY filename DESC;", applicationID);
MYSQL_RES *result = 0;
//sqlCommandMutex.Lock();
if (!ExecuteBlockingCommand(query, &result))
{
Rollback();
//sqlCommandMutex.Unlock();
return false;
}
//sqlCommandMutex.Unlock();
DataStructures::List <FileInfo> newestFiles;
MYSQL_ROW row;
while ((row = mysql_fetch_row (result)) != 0)
{
FileInfo fi;
fi.filename = row [0];
fi.createFile = (atoi (row [2]) != 0);
if (fi.createFile)
{
RakAssert(mysql_fetch_lengths (result) [1] == HASH_LENGTH); // check the data is sensible
memcpy (fi.contentHash, row [1], HASH_LENGTH);
}
newestFiles.Insert (fi, _FILE_AND_LINE_ );
}
mysql_free_result(result);
FileList newFiles;
// Loop through files on filesOnHarddrive
// If the file in filesOnHarddrive does not exist in the query result, or if it does but the hash is different or non-existent, add this file to the create list
for (unsigned fileListIndex=0; fileListIndex < filesOnHarddrive.fileList.Size(); fileListIndex++)
{
bool addFile=true;
if (fileListIndex%10==0)
printf("Hashing files %i/%i\n", fileListIndex+1, filesOnHarddrive.fileList.Size());
const char * hardDriveFilename=filesOnHarddrive.fileList[fileListIndex].filename;
const char * hardDriveHash=filesOnHarddrive.fileList[fileListIndex].data;
for (unsigned i = 0; i != newestFiles.Size (); ++i)
{
const FileInfo & fi = newestFiles [i];
if (_stricmp(hardDriveFilename, fi.filename)==0)
{
if (fi.createFile && memcmp(fi.contentHash, hardDriveHash, HASH_LENGTH)==0)
{
// File exists in database and is the same
addFile=false;
}
break;
}
}
// Unless set to false, file does not exist in query result or is different.
if (addFile)
{
newFiles.AddFile(hardDriveFilename,hardDriveFilename, filesOnHarddrive.fileList[fileListIndex].data, filesOnHarddrive.fileList[fileListIndex].dataLengthBytes, filesOnHarddrive.fileList[fileListIndex].fileLengthBytes, FileListNodeContext(), false, true);
filesOnHarddrive.fileList[fileListIndex].data=0;
}
}
// Go through query results that are marked as create
// If a file that is currently in the database is not on the harddrive, add it to the delete list
FileList deletedFiles;
for (unsigned i = 0; i != newestFiles.Size (); ++i)
{
const FileInfo & fi = newestFiles [i];
if (!fi.createFile)
continue; // If already false don't mark false again.
bool fileOnHarddrive=false;
for (unsigned fileListIndex=0; fileListIndex < filesOnHarddrive.fileList.Size(); fileListIndex++)
{
const char * hardDriveFilename=filesOnHarddrive.fileList[fileListIndex].filename;
//hardDriveHash=filesOnHarddrive.fileList[fileListIndex].data;
if (_stricmp(hardDriveFilename, fi.filename)==0)
{
fileOnHarddrive=true;
break;
}
}
if (!fileOnHarddrive)
deletedFiles.AddFile(fi.filename,fi.filename,0,0,0,FileListNodeContext(), false);
}
// files on harddrive no longer needed. Free this memory since generating all the patches is memory intensive.
filesOnHarddrive.Clear();
// For each file in the delete list add a row indicating file deletion
for (unsigned fileListIndex=0; fileListIndex < deletedFiles.fileList.Size(); fileListIndex++)
{
if (fileListIndex%10==0)
printf("Tagging deleted files %i/%i\n", fileListIndex+1, deletedFiles.fileList.Size());
sprintf_s(query, "INSERT INTO FileVersionHistory(applicationID, filename, createFile, changeSetID, userName) VALUES (%i, '%s', FALSE,%i,'%s');",
applicationID, GetEscapedString(deletedFiles.fileList[fileListIndex].filename).C_String(), changeSetId, GetEscapedString(userName).C_String());
//sqlCommandMutex.Lock();
if (!ExecuteBlockingCommand (query))
{
Rollback();
//sqlCommandMutex.Unlock();
deletedFiles.Clear();
newFiles.Clear();
return false;
}
//sqlCommandMutex.Unlock();
}
// Clear the delete list as it is no longer needed.
deletedFiles.Clear();
// For each file in the create list
for (unsigned fileListIndex=0; fileListIndex < newFiles.fileList.Size(); fileListIndex++)
{
if (fileListIndex%10==0)
printf("Adding file %i/%i\n", fileListIndex+1, newFiles.fileList.Size());
const char * hardDriveFilename=newFiles.fileList[fileListIndex].filename;
const char * hardDriveData=newFiles.fileList[fileListIndex].data+HASH_LENGTH;
const char * hardDriveHash=newFiles.fileList[fileListIndex].data;
unsigned hardDriveDataLength=newFiles.fileList[fileListIndex].fileLengthBytes;
sprintf_s( query, "SELECT fileID from FileVersionHistory WHERE applicationID=%i AND filename='%s' AND createFile=TRUE;", applicationID, GetEscapedString(hardDriveFilename).C_String() );
MYSQL_RES * res = 0;
//sqlCommandMutex.Lock();
if (!ExecuteBlockingCommand (query, &res))
{
Rollback();
//sqlCommandMutex.Unlock();
newFiles.Clear();
return false;
}
//sqlCommandMutex.Unlock();
// Create new patches for every create version
while ((row = mysql_fetch_row (res)) != 0)
{
const char * fileID = row [0];
// The last query handled all the relevant comparisons
sprintf_s(query, "SELECT content from FileVersionHistory WHERE fileID=%s", fileID );
MYSQL_RES * queryResult = 0;
//sqlCommandMutex.Lock();
if (!ExecuteBlockingCommand (query, &queryResult))
{
Rollback();
//sqlCommandMutex.Unlock();
newFiles.Clear();
mysql_free_result(res);
return false;
}
//sqlCommandMutex.Unlock();
MYSQL_ROW queryRow = mysql_fetch_row (queryResult);
const unsigned contentLength=mysql_fetch_lengths (queryResult) [0];
const char * content=queryRow [0];
char *patch;
unsigned patchLength;
if (!CreatePatch(content, contentLength, (char *) hardDriveData, hardDriveDataLength, &patch, &patchLength))
{
strcpy_s(lastError,"CreatePatch failed.\n");
Rollback();
newFiles.Clear();
mysql_free_result(res);
mysql_free_result(queryResult);
return false;
}
char buf[512];
stmt = mysql_stmt_init(mySqlConnection);
sprintf (buf, "UPDATE FileVersionHistory SET patch=? where fileID=%s;", fileID);
if ((prepareResult=mysql_stmt_prepare(stmt, buf, (unsigned long) strlen(buf)))!=0)
{
strcpy (lastError, mysql_stmt_error (stmt));
mysql_stmt_close(stmt);
Rollback();
return false;
}
memset(bind, 0, sizeof(bind));
unsigned long l1;
l1=patchLength;
bind[0].buffer_type= MYSQL_TYPE_LONG_BLOB;
bind[0].buffer= patch;
bind[0].buffer_length= patchLength;
bind[0].is_null= &falseVar;
bind[0].length=&l1;
if (mysql_stmt_bind_param(stmt, bind))
{
strcpy (lastError, mysql_stmt_error (stmt));
mysql_stmt_close(stmt);
Rollback();
return false;
}
//sqlCommandMutex.Lock();
if (mysql_stmt_execute(stmt))
{
strcpy (lastError, mysql_stmt_error (stmt));
mysql_stmt_close(stmt);
Rollback();
//sqlCommandMutex.Unlock();
newFiles.Clear();
mysql_free_result(res);
mysql_free_result(queryResult);
delete [] patch;
return false;
}
//sqlCommandMutex.Unlock();
mysql_stmt_close(stmt);
delete [] patch;
mysql_free_result(queryResult);
}
mysql_free_result(res);
stmt = mysql_stmt_init(mySqlConnection);
sprintf_s(query, "INSERT INTO FileVersionHistory (applicationID, filename, fileLength, content, contentHash, createFile, changeSetID, userName) "
"VALUES (%i, ?, %i,?,?, TRUE, %i, '%s' );",
applicationID, hardDriveDataLength, changeSetId, GetEscapedString(userName).C_String());
if ((prepareResult=mysql_stmt_prepare(stmt, query, (unsigned long) strlen(query)))!=0)
{
strcpy (lastError, mysql_stmt_error (stmt));
mysql_stmt_close(stmt);
Rollback();
return false;
}
memset(bind, 0, sizeof(bind));
unsigned long l2,l3,l4;
l2=(unsigned long) strlen(hardDriveFilename);
l3=hardDriveDataLength;
l4=HASH_LENGTH;
bind[0].buffer_type= MYSQL_TYPE_STRING;
bind[0].buffer= (void*) hardDriveFilename;
bind[0].buffer_length= (unsigned long) strlen(hardDriveFilename);
bind[0].is_null= &falseVar;
bind[0].length=&l2;
bind[1].buffer_type= MYSQL_TYPE_LONG_BLOB;
bind[1].buffer= (void*) hardDriveData;
bind[1].buffer_length= hardDriveDataLength;
bind[1].is_null= &falseVar;
bind[1].length=&l3;
bind[2].buffer_type= MYSQL_TYPE_TINY_BLOB;
bind[2].buffer= (void*) hardDriveHash;
bind[2].buffer_length= HASH_LENGTH;
bind[2].is_null= &falseVar;
bind[2].length=&l4;
if (mysql_stmt_bind_param(stmt, bind))
{
strcpy (lastError, mysql_stmt_error (stmt));
mysql_stmt_close(stmt);
Rollback();
return false;
}
//sqlCommandMutex.Lock();
if (mysql_stmt_execute(stmt))
{
strcpy (lastError, mysql_stmt_error (stmt));
mysql_stmt_close(stmt);
Rollback();
//sqlCommandMutex.Unlock();
return false;
}
//sqlCommandMutex.Unlock();
mysql_stmt_close(stmt);
}
//sqlCommandMutex.Lock();
if (!ExecuteBlockingCommand("COMMIT;"))
{
Rollback ();
//sqlCommandMutex.Unlock();
return false;
}
//sqlCommandMutex.Unlock();
return true;
}
const char *AutopatcherMySQLRepository::GetLastError(void) const
{
return MySQLInterface::GetLastError();
}
unsigned int AutopatcherMySQLRepository::GetFilePart( const char *filename, unsigned int startReadBytes, unsigned int numBytesToRead, void *preallocatedDestination, FileListNodeContext context)
{
// unused parameters
(void)filename;
char query[512];
sprintf_s(query, "SELECT substring(content from %i for %i) FROM FileVersionHistory WHERE fileId=%i;", startReadBytes+1,numBytesToRead,context.flnc_extraData1);
// CREATE NEW CONNECTION JUST FOR THIS QUERY
// This is because the autopatcher is sharing this class, but this is called from multiple threads and mysql is not threadsafe
MYSQL_RES * result;
char error[512];
filePartConnectionMutex.Lock();
if (filePartConnection==0)
{
filePartConnection = mysql_init(0);
mysql_real_connect (filePartConnection, _host, _user, _passwd, _db, _port, _unix_socket, _clientflag);
}
if (mysql_query(filePartConnection, query)!=0)
{
// #med - review --- should this set the class member maybe?
strcpy (error, mysql_error (filePartConnection));
}
result = mysql_store_result (filePartConnection);
if (result)
{
// This has very poor performance with any size for GetIncrementalReadChunkSize, but especially for larger sizes
MYSQL_ROW row = mysql_fetch_row (result);
if (row != 0)
{
const char * content = row [0];
unsigned long contentLength=mysql_fetch_lengths (result) [0];
memcpy(preallocatedDestination,content,contentLength);
mysql_free_result (result);
filePartConnectionMutex.Unlock();
return contentLength;
}
mysql_free_result (result);
}
filePartConnectionMutex.Unlock();
return 0;
}
const int AutopatcherMySQLRepository::GetIncrementalReadChunkSize(void) const
{
// AutopatcherMySQLRepository::GetFilePart is extremely slow with larger files
return 262144*4;
}

View File

@ -0,0 +1,110 @@
/*
* 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.
*/
/// \file
/// \brief An implementation of the AutopatcherRepositoryInterface to use MySQL to store the relevant data
///
#ifndef __MYSQL_REPOSITORY_H
#define __MYSQL_REPOSITORY_H
#include "slikenet/AutopatcherRepositoryInterface.h"
#include "MySQLInterface.h"
#include "slikenet/Export.h"
namespace SLNet
{
class FileListProgress;
/// \ingroup Autopatcher
/// An implementation of the AutopatcherRepositoryInterface to use MySQL to store the relevant data
class RAK_DLL_EXPORT AutopatcherMySQLRepository : public AutopatcherRepositoryInterface, public MySQLInterface
{
public:
AutopatcherMySQLRepository();
~AutopatcherMySQLRepository();
/// Create the tables used by the autopatcher, for all applications. Call this first.
/// \return True on success, false on failure.
bool CreateAutopatcherTables(void);
/// Destroy the tables used by the autopatcher. Don't call this unless you don't want to use the autopatcher anymore, or are testing.
/// \return True on success, false on failure.
bool DestroyAutopatcherTables(void);
/// Add an application for use by files. Call this second.
/// \param[in] applicationName A null terminated string.
/// \param[in] userName Stored in the database, but otherwise unused. Useful to track who added this application.
/// \return True on success, false on failure.
bool AddApplication(const char *applicationName, const char *userName);
/// Remove an application and files used by that application.
/// \param[in] applicationName A null terminated string previously passed to AddApplication
/// \return True on success, false on failure.
bool RemoveApplication(const char *applicationName);
/// Update all the files for an application to match what is at the specified directory. Call this third.
/// Be careful not to call this with the wrong directory.
/// This is implemented in a Begin and Rollback block so you won't a messed up database from get partial updates.
/// \param[in] applicationName A null terminated string previously passed to AddApplication
/// \param[in] applicationDirectory The base directory of your application. All files in this directory and subdirectories are added.
/// \param[in] userName Stored in the database, but otherwise unused. Useful to track who added this revision
/// \param[in] cb Callback to get progress updates. Pass 0 to not use.
/// \return True on success, false on failure.
bool UpdateApplicationFiles(const char *applicationName, const char *applicationDirectory, const char *userName, FileListProgress *cb);
/// Get list of files added and deleted since a certain date. This is used by AutopatcherServer and not usually explicitly called.
/// \param[in] applicationName A null terminated string previously passed to AddApplication
/// \param[out] addedFiles A list of the current versions of filenames with SHA1_LENGTH byte hashes as their data that were created after \a sinceData
/// \param[out] deletedFiles A list of the current versions of filenames that were deleted after \a sinceData
/// \param[in] An input date, in the string format of a timestamp.
/// \return True on success, false on failure.
virtual bool GetChangelistSinceDate(const char *applicationName, FileList *addedOrModifiedFilesWithHashData, FileList *deletedFiles, double sinceDate);
/// Get patches (or files) for every file in input, assuming that input has a hash for each of those files. This is used by AutopatcherServer and not usually explicitly called.
/// \param[in] applicationName A null terminated string previously passed to AddApplication
/// \param[in] input A list of files with hashes to get from the database. If this hash exists, a patch to the current version is returned if this file is not the current version. Otherwise the current version is returned.
/// \param[out] patchList A list of files with either the filedata or the patch. This is a subset of \a input. The context data for each file will be either PC_WRITE_FILE (to just write the file) or PC_HASH_WITH_PATCH (to patch). If PC_HASH_WITH_PATCH, then the file contains a SHA1_LENGTH byte patch followed by the hash. The datalength is patchlength + SHA1_LENGTH
/// \return 1 on success, 0 on database failure, -1 on tried to download original unmodified file
virtual int GetPatches(const char *applicationName, FileList *input, bool allowDownloadOfOriginalUnmodifiedFiles, FileList *patchList);
// Not yet implemented
virtual bool GetMostRecentChangelistWithPatches(SLNet::RakString &applicationName, FileList *patchedFiles, FileList *addedFiles, FileList *addedOrModifiedFileHashes, FileList *deletedFiles, double *priorRowPatchTime, double *mostRecentRowPatchTime);
/// If any of the above functions fail, the error string is stored internally. Call this to get it.
virtual const char *GetLastError(void) const;
/// Read part of a file into \a destination
/// Return the number of bytes written. Return 0 when file is done.
/// \param[in] filename Filename to read
/// \param[in] startReadBytes What offset from the start of the file to read from
/// \param[in] numBytesToRead How many bytes to read. This is also how many bytes have been allocated to preallocatedDestination
/// \param[out] preallocatedDestination Write your data here
/// \return The number of bytes read, or 0 if none
virtual unsigned int GetFilePart( const char *filename, unsigned int startReadBytes, unsigned int numBytesToRead, void *preallocatedDestination, FileListNodeContext context);
/// \return Passed to FileListTransfer::Send() as the _chunkSize parameter.
virtual const int GetIncrementalReadChunkSize(void) const;
st_mysql *filePartConnection;
SimpleMutex filePartConnectionMutex;
};
} // namespace SLNet
#endif

View File

@ -0,0 +1,208 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug - Unicode|Win32">
<Configuration>Debug - Unicode</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release - Unicode|Win32">
<Configuration>Release - Unicode</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Retail - Unicode|Win32">
<Configuration>Retail - Unicode</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Retail|Win32">
<Configuration>Retail</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{9092C339-3CCA-41B4-8B80-FAED313D4168}</ProjectGuid>
<RootNamespace>AutopatcherMySQLRepository</RootNamespace>
<Keyword>Win32Proj</Keyword>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Retail|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release - Unicode|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Retail - Unicode|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug - Unicode|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Retail|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release - Unicode|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Retail - Unicode|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug - Unicode|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<_ProjectFileVersion>10.0.40219.1</_ProjectFileVersion>
<OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Configuration)\</OutDir>
<OutDir Condition="'$(Configuration)|$(Platform)'=='Debug - Unicode|Win32'">$(Configuration)\</OutDir>
<OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Configuration)\</OutDir>
<OutDir Condition="'$(Configuration)|$(Platform)'=='Retail|Win32'">$(Configuration)\</OutDir>
<OutDir Condition="'$(Configuration)|$(Platform)'=='Release - Unicode|Win32'">$(Configuration)\</OutDir>
<OutDir Condition="'$(Configuration)|$(Platform)'=='Retail - Unicode|Win32'">$(Configuration)\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>./../;./../../bzip2-1.0.6;./../../../Source;./../../MySQLInterface;C:\Program Files (x86)\MySQL\MySQL Server 5.1\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;WIN32;_DEBUG;_LIB;__WIN__;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>true</MinimalRebuild>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<TreatWarningAsError>true</TreatWarningAsError>
<SDLCheck>true</SDLCheck>
</ClCompile>
<Lib />
<Lib>
<TreatLibWarningAsErrors>true</TreatLibWarningAsErrors>
</Lib>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug - Unicode|Win32'">
<ClCompile>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>./../;./../../bzip2-1.0.6;./../../../Source;./../../MySQLInterface;C:\Program Files (x86)\MySQL\MySQL Server 5.1\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;WIN32;_DEBUG;_LIB;__WIN__;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>true</MinimalRebuild>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<TreatWarningAsError>true</TreatWarningAsError>
<SDLCheck>true</SDLCheck>
</ClCompile>
<Lib />
<Lib>
<TreatLibWarningAsErrors>true</TreatLibWarningAsErrors>
</Lib>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<AdditionalIncludeDirectories>./../;./../../bzip2-1.0.6;./../../../Source;./../../MySQLInterface;C:\Program Files (x86)\MySQL\MySQL Server 5.1\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;WIN32;NDEBUG;_LIB;__WIN__;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<TreatWarningAsError>true</TreatWarningAsError>
<MinimalRebuild>true</MinimalRebuild>
<SDLCheck>true</SDLCheck>
</ClCompile>
<Lib />
<Lib>
<TreatLibWarningAsErrors>true</TreatLibWarningAsErrors>
</Lib>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Retail|Win32'">
<ClCompile>
<AdditionalIncludeDirectories>./../;./../../bzip2-1.0.6;./../../../Source;./../../MySQLInterface;C:\Program Files (x86)\MySQL\MySQL Server 5.1\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;WIN32;_RETAIL;NDEBUG;_LIB;__WIN__;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<TreatWarningAsError>true</TreatWarningAsError>
<MinimalRebuild>true</MinimalRebuild>
<BufferSecurityCheck>false</BufferSecurityCheck>
</ClCompile>
<Lib />
<Lib>
<TreatLibWarningAsErrors>true</TreatLibWarningAsErrors>
</Lib>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release - Unicode|Win32'">
<ClCompile>
<AdditionalIncludeDirectories>./../;./../../bzip2-1.0.6;./../../../Source;./../../MySQLInterface;C:\Program Files (x86)\MySQL\MySQL Server 5.1\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;WIN32;NDEBUG;_LIB;__WIN__;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<TreatWarningAsError>true</TreatWarningAsError>
<MinimalRebuild>true</MinimalRebuild>
<SDLCheck>true</SDLCheck>
</ClCompile>
<Lib />
<Lib>
<TreatLibWarningAsErrors>true</TreatLibWarningAsErrors>
</Lib>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Retail - Unicode|Win32'">
<ClCompile>
<AdditionalIncludeDirectories>./../;./../../bzip2-1.0.6;./../../../Source;./../../MySQLInterface;C:\Program Files (x86)\MySQL\MySQL Server 5.1\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;WIN32;_RETAIL;NDEBUG;_LIB;__WIN__;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<TreatWarningAsError>true</TreatWarningAsError>
<MinimalRebuild>true</MinimalRebuild>
<BufferSecurityCheck>false</BufferSecurityCheck>
</ClCompile>
<Lib />
<Lib>
<TreatLibWarningAsErrors>true</TreatLibWarningAsErrors>
</Lib>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\ApplyPatch.cpp" />
<ClCompile Include="AutopatcherMySQLRepository.cpp" />
<ClCompile Include="..\..\MySQLInterface\MySQLInterface.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\ApplyPatch.h" />
<ClInclude Include="AutopatcherMySQLRepository.h" />
<ClInclude Include="..\..\MySQLInterface\MySQLInterface.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\ApplyPatch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="AutopatcherMySQLRepository.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\MySQLInterface\MySQLInterface.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\ApplyPatch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="AutopatcherMySQLRepository.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\MySQLInterface\MySQLInterface.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@ -0,0 +1,27 @@
#
# This file was taken from RakNet 4.082.
# Please see licenses/RakNet license.txt for the underlying license and related copyright.
#
#
# Modified work: Copyright (c) 2017-2019, 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.
#
project(AutopatcherMySQLRepository)
FINDMYSQL()
IF(WIN32 AND NOT UNIX)
FILE(GLOB ALL_HEADER_SRCS *.h ${MySQLInterface_SOURCE_DIR}/MySQLInterFace.h ${Autopatcher_SOURCE_DIR}/ApplyPatch.h)
FILE(GLOB ALL_CPP_SRCS *.cpp ${MySQLInterface_SOURCE_DIR}/MySQLInterFace.cpp ${Autopatcher_SOURCE_DIR}/ApplyPatch.cpp)
include_directories(${SLIKENET_HEADER_FILES} ./ ${MySQLInterface_SOURCE_DIR} ${Autopatcher_SOURCE_DIR} ${MYSQL_INCLUDE_DIR} ${BZip2_SOURCE_DIR})
add_library(AutoPatcherMySQLRepository STATIC ${ALL_CPP_SRCS} ${ALL_HEADER_SRCS})
target_link_libraries (AutoPatcherMySQLRepository ${SLIKENET_COMMON_LIBS} ${MYSQL_LIBRARIES})
VSUBFOLDER(AutoPatcherMySQLRepository "Samples/AutoPatcher/Server/MySQL")
ELSE(WIN32 AND NOT UNIX)
FILE(GLOB ALL_HEADER_SRCS *.h)
FILE(GLOB ALL_CPP_SRCS *.cpp)
include_directories(${SLIKENET_HEADER_FILES} ./ ${MySQLInterface_SOURCE_DIR} ${Autopatcher_SOURCE_DIR} ${MYSQL_INCLUDE_DIR})
add_library(AutoPatcherMySQLRepository STATIC ${ALL_CPP_SRCS} ${ALL_HEADER_SRCS})
target_link_libraries (AutoPatcherMySQLRepository ${SLIKENET_COMMON_LIBS} LibAutopatcher LibMySQLInterface ${MYSQL_LIBRARIES})
ENDIF(WIN32 AND NOT UNIX)

View File

@ -0,0 +1,54 @@
# - Find MySQL
# Find the MySQL includes and client library
# This module defines
# MYSQL_INCLUDE_DIR, where to find mysql.h
# MYSQL_LIBRARIES, the libraries needed to use MySQL.
# MYSQL_FOUND, If false, do not try to use MySQL.
#
# Copyright (c) 2006, Jaroslaw Staniek, <js@iidea.pl>
#
# Redistribution and use is allowed according to the terms of the BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
#
# Note: The referenced COPYING-CMAKE-SCRIPTS file was not present in the RakNet
# repository. We also failed to get in touch with the copyright holder by mail
# to clarify the situation. To the best of our knowledge the referenced license
# is the Modified BSD License.
if(MYSQL_INCLUDE_DIR AND MYSQL_LIBRARIES)
set(MYSQL_FOUND TRUE)
else(MYSQL_INCLUDE_DIR AND MYSQL_LIBRARIES)
find_path(MYSQL_INCLUDE_DIR mysql.h
/usr/include/mysql
/usr/local/include/mysql
$ENV{ProgramFiles}/MySQL/*/include
$ENV{SystemDrive}/MySQL/*/include
)
if(WIN32 AND MSVC)
find_library(MYSQL_LIBRARIES NAMES libmysql
PATHS
$ENV{ProgramFiles}/MySQL/*/lib/opt
$ENV{SystemDrive}/MySQL/*/lib/opt
)
else(WIN32 AND MSVC)
find_library(MYSQL_LIBRARIES NAMES mysqlclient
PATHS
/usr/lib/mysql
/usr/local/lib/mysql
)
endif(WIN32 AND MSVC)
if(MYSQL_INCLUDE_DIR AND MYSQL_LIBRARIES)
set(MYSQL_FOUND TRUE)
message(STATUS "Found MySQL: ${MYSQL_INCLUDE_DIR}, ${MYSQL_LIBRARIES}")
else(MYSQL_INCLUDE_DIR AND MYSQL_LIBRARIES)
set(MYSQL_FOUND FALSE)
message(STATUS "MySQL not found.")
endif(MYSQL_INCLUDE_DIR AND MYSQL_LIBRARIES)
mark_as_advanced(MYSQL_INCLUDE_DIR MYSQL_LIBRARIES)
endif(MYSQL_INCLUDE_DIR AND MYSQL_LIBRARIES)

View File

@ -0,0 +1,156 @@
/*
* 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.
*/
/// \file
/// \brief An implementation of the AutopatcherRepositoryInterface to use PostgreSQL to store the relevant data
#define _USE_POSTGRE_REPOSITORY
#ifdef _USE_POSTGRE_REPOSITORY
#include "slikenet/AutopatcherRepositoryInterface.h"
#include "PostgreSQLInterface.h"
#include "slikenet/Export.h"
struct pg_conn;
typedef struct pg_conn PGconn;
struct pg_result;
typedef struct pg_result PGresult;
#ifndef __POSTGRE_REPOSITORY_H
#define __POSTGRE_REPOSITORY_H
namespace SLNet
{
class FileListProgress;
/// \ingroup Autopatcher
/// An implementation of the AutopatcherRepositoryInterface to use PostgreSQL to store the relevant data
class RAK_DLL_EXPORT AutopatcherPostgreRepository : public AutopatcherRepositoryInterface, public PostgreSQLInterface
{
public:
AutopatcherPostgreRepository();
virtual ~AutopatcherPostgreRepository();
/// Create the tables used by the autopatcher, for all applications. Call this first.
/// \return True on success, false on failure.
virtual bool CreateAutopatcherTables(void);
/// Destroy the tables used by the autopatcher. Don't call this unless you don't want to use the autopatcher anymore, or are testing.
/// \return True on success, false on failure.
bool DestroyAutopatcherTables(void);
/// Add an application for use by files. Call this second.
/// \param[in] applicationName A null terminated string.
/// \param[in] userName Stored in the database, but otherwise unused. Useful to track who added this application.
/// \return True on success, false on failure.
bool AddApplication(const char *applicationName, const char *userName);
/// Remove an application and files used by that application.
/// \param[in] applicationName A null terminated string previously passed to AddApplication
/// \return True on success, false on failure.
bool RemoveApplication(const char *applicationName);
/// Update all the files for an application to match what is at the specified directory. Call this third.
/// Be careful not to call this with the wrong directory.
/// This is implemented in a Begin and Rollback block so you won't a messed up database from get partial updates.
/// \note It takes 10 bytes of memory to create a patch per byte on disk for a file. So you should not have any files larger than 1/10th your server memory.
/// \param[in] applicationName A null terminated string previously passed to AddApplication
/// \param[in] applicationDirectory The base directory of your application. All files in this directory and subdirectories are added.
/// \param[in] userName Stored in the database, but otherwise unused. Useful to track who added this revision
/// \param[in] cb Callback to get progress updates. Pass 0 to not use.
/// \return True on success, false on failure.
virtual bool UpdateApplicationFiles(const char *applicationName, const char *applicationDirectory, const char *userName, FileListProgress *cb);
/// Get list of files added and deleted since a certain date. This is used by AutopatcherServer and not usually explicitly called.
/// \param[in] applicationName A null terminated string previously passed to AddApplication
/// \param[out] addedFiles A list of the current versions of filenames with SHA1_LENGTH byte hashes as their data that were created after \a sinceData
/// \param[out] deletedFiles A list of the current versions of filenames that were deleted after \a sinceData
/// \param[in] sinceDate
/// \return True on success, false on failure.
virtual bool GetChangelistSinceDate(const char *applicationName, FileList *addedOrModifiedFilesWithHashData, FileList *deletedFiles, double sinceDate);
/// Get patches (or files) for every file in input, assuming that input has a hash for each of those files. This is used by AutopatcherServer and not usually explicitly called.
/// \param[in] applicationName A null terminated string previously passed to AddApplication
/// \param[in] input A list of files with hashes to get from the database. If this hash exists, a patch to the current version is returned if this file is not the current version. Otherwise the current version is returned.
/// \param[in] allowDownloadOfOriginalUnmodifiedFiles If false, then if a file has never been modified and there is no hash for it in the input list, return false. This is to prevent clients from just downloading the game from the autopatcher.
/// \param[out] patchList A list of files with either the filedata or the patch. This is a subset of \a input. The context data for each file will be either PC_WRITE_FILE (to just write the file) or PC_HASH_WITH_PATCH (to patch). If PC_HASH_WITH_PATCH, then the file contains a SHA1_LENGTH byte patch followed by the hash. The datalength is patchlength + SHA1_LENGTH
/// \return 1 on success, 0 on database failure, -1 on tried to download original unmodified file
virtual int GetPatches(const char *applicationName, FileList *input, bool allowDownloadOfOriginalUnmodifiedFiles, FileList *patchList);
/// For the most recent update, return files that were patched, added, or deleted. For files that were patched, return both the patch in \a patchedFiles and the current version in \a updatedFiles
/// \param[in,out] applicationName Name of the application to get patches for. If empty, uses the most recently updated application, and the string will be updated to reflect this name.
/// \param[out] patchedFiles A list of patched files with op PC_HASH_2_WITH_PATCH. It has 2 hashes, the priorHash and the currentHash. The currentHash is checked on the client after patching for patch success. The priorHash is checked in AutopatcherServer::OnGetPatch() to see if the client is able to hash with the version they currently have
/// \param[out] patchedFiles A list of new files. It contains the actual data in addition to the filename
/// \param[out] addedOrModifiedFileHashes A list of file hashes that were either modified or new. This is returned to the client when replying to ID_AUTOPATCHER_CREATION_LIST, which tells the client what files have changed on the server since a certain date
/// \param[out] deletedFiles A list of the current versions of filenames that were deleted in the most recent patch
/// \param[out] whenPatched time in seconds since epoch when patched. Use time() function to get this in C
/// \return true on success, false on failure
virtual bool GetMostRecentChangelistWithPatches(SLNet::RakString &applicationName, FileList *patchedFiles, FileList *addedFiles, FileList *addedOrModifiedFileHashes, FileList *deletedFiles, double *priorRowPatchTime, double *mostRecentRowPatchTime);
/// If any of the above functions fail, the error string is stored internally. Call this to get it.
virtual const char *GetLastError(void) const;
/// Read part of a file into \a destination
/// Return the number of bytes written. Return 0 when file is done.
/// \param[in] filename Filename to read
/// \param[in] startReadBytes What offset from the start of the file to read from
/// \param[in] numBytesToRead How many bytes to read. This is also how many bytes have been allocated to preallocatedDestination
/// \param[out] preallocatedDestination Write your data here
/// \return The number of bytes read, or 0 if none
virtual unsigned int GetFilePart( const char *filename, unsigned int startReadBytes, unsigned int numBytesToRead, void *preallocatedDestination, FileListNodeContext context);
/// \return Passed to FileListTransfer::Send() as the _chunkSize parameter.
virtual const int GetIncrementalReadChunkSize(void) const;
// Use a separate connection for file parts, because PGConn is not threadsafe
PGconn *filePartConnection;
SimpleMutex filePartConnectionMutex;
protected:
virtual unsigned int GetPatchPart( const char *filename, unsigned int startReadBytes, unsigned int numBytesToRead, void *preallocatedDestination, FileListNodeContext context);
};
// This version references the version on the harddrive, rather than store the patch in the database
// It is necessary if your game has files over about 400 megabytes.
class RAK_DLL_EXPORT AutopatcherPostgreRepository2 : public AutopatcherPostgreRepository
{
public:
virtual bool CreateAutopatcherTables(void);
virtual bool GetMostRecentChangelistWithPatches(SLNet::RakString &applicationName, FileList *patchedFiles, FileList *addedFiles, FileList *addedOrModifiedFileHashes, FileList *deletedFiles, double *priorRowPatchTime, double *mostRecentRowPatchTime);
virtual bool UpdateApplicationFiles(const char *applicationName, const char *applicationDirectory, const char *userName, FileListProgress *cb);
virtual unsigned int GetFilePart( const char *filename, unsigned int startReadBytes, unsigned int numBytesToRead, void *preallocatedDestination, FileListNodeContext context);
/// Can override this to create patches using a different tool
/// \param[in] oldFile Path to the old version of the file, on disk
/// \param[in] newFile Path to the updated file, on disk
/// \param[out] patch Pointer you should allocate, to hold the patch
/// \param[out] patchLength Write the length of the resultant patch here
/// \param[out] patchAlgorithm Stored in the database. Use if you want to represent what algorithm was used. Transmitted to the client for decompression
virtual int MakePatch(const char *oldFile, const char *newFile, char **patch, unsigned int *patchLength, int *patchAlgorithm);
protected:
// Implements MakePatch using bsDiff. Uses a lot of memory, should not use for files above about 100 megabytes.
virtual bool MakePatchBSDiff(FILE *fpOld, int contentLengthOld, FILE *fpNew, int contentLengthNew, char **patch, unsigned int *patchLength);
};
} // namespace SLNet
#endif
#endif

View File

@ -0,0 +1,221 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug - Unicode|Win32">
<Configuration>Debug - Unicode</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release - Unicode|Win32">
<Configuration>Release - Unicode</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Retail - Unicode|Win32">
<Configuration>Retail - Unicode</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Retail|Win32">
<Configuration>Retail</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectName>AutopatcherPostgreSQLRepository</ProjectName>
<ProjectGuid>{0FD54BD0-C49C-4681-80CE-AA22B8995CA8}</ProjectGuid>
<RootNamespace>AutopatcherPostgreSQLRepository</RootNamespace>
<Keyword>Win32Proj</Keyword>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Retail|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release - Unicode|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Retail - Unicode|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug - Unicode|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Retail|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release - Unicode|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Retail - Unicode|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug - Unicode|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<_ProjectFileVersion>10.0.40219.1</_ProjectFileVersion>
<OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Configuration)\</OutDir>
<OutDir Condition="'$(Configuration)|$(Platform)'=='Debug - Unicode|Win32'">$(Configuration)\</OutDir>
<OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Configuration)\</OutDir>
<OutDir Condition="'$(Configuration)|$(Platform)'=='Retail|Win32'">$(Configuration)\</OutDir>
<OutDir Condition="'$(Configuration)|$(Platform)'=='Release - Unicode|Win32'">$(Configuration)\</OutDir>
<OutDir Condition="'$(Configuration)|$(Platform)'=='Retail - Unicode|Win32'">$(Configuration)\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>./../;./../../bzip2-1.0.6;./../../../Source;./../../PostgreSQLInterface;C:\Program Files (x86)\PostgreSQL\9.2\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>true</MinimalRebuild>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<TreatWarningAsError>true</TreatWarningAsError>
<SDLCheck>true</SDLCheck>
</ClCompile>
<Lib />
<Lib>
<TreatLibWarningAsErrors>true</TreatLibWarningAsErrors>
</Lib>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug - Unicode|Win32'">
<ClCompile>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>./../;./../../bzip2-1.0.6;./../../../Source;./../../PostgreSQLInterface;C:\Program Files (x86)\PostgreSQL\9.2\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>true</MinimalRebuild>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<TreatWarningAsError>true</TreatWarningAsError>
<SDLCheck>true</SDLCheck>
</ClCompile>
<Lib />
<Lib>
<TreatLibWarningAsErrors>true</TreatLibWarningAsErrors>
</Lib>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<AdditionalIncludeDirectories>./../;./../../bzip2-1.0.6;./../../../Source;./../../PostgreSQLInterface;C:\Program Files (x86)\PostgreSQL\9.2\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<TreatWarningAsError>true</TreatWarningAsError>
<MinimalRebuild>true</MinimalRebuild>
<SDLCheck>true</SDLCheck>
</ClCompile>
<Lib />
<Lib>
<TreatLibWarningAsErrors>true</TreatLibWarningAsErrors>
</Lib>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Retail|Win32'">
<ClCompile>
<AdditionalIncludeDirectories>./../;./../../bzip2-1.0.6;./../../../Source;./../../PostgreSQLInterface;C:\Program Files (x86)\PostgreSQL\9.2\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;WIN32;_RETAIL;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<TreatWarningAsError>true</TreatWarningAsError>
<MinimalRebuild>true</MinimalRebuild>
<BufferSecurityCheck>false</BufferSecurityCheck>
</ClCompile>
<Lib />
<Lib>
<TreatLibWarningAsErrors>true</TreatLibWarningAsErrors>
</Lib>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release - Unicode|Win32'">
<ClCompile>
<AdditionalIncludeDirectories>./../;./../../bzip2-1.0.6;./../../../Source;./../../PostgreSQLInterface;C:\Program Files (x86)\PostgreSQL\9.2\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<TreatWarningAsError>true</TreatWarningAsError>
<MinimalRebuild>true</MinimalRebuild>
<SDLCheck>true</SDLCheck>
</ClCompile>
<Lib />
<Lib>
<TreatLibWarningAsErrors>true</TreatLibWarningAsErrors>
</Lib>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Retail - Unicode|Win32'">
<ClCompile>
<AdditionalIncludeDirectories>./../;./../../bzip2-1.0.6;./../../../Source;./../../PostgreSQLInterface;C:\Program Files (x86)\PostgreSQL\9.2\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;WIN32;_RETAIL;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<TreatWarningAsError>true</TreatWarningAsError>
<MinimalRebuild>true</MinimalRebuild>
<BufferSecurityCheck>false</BufferSecurityCheck>
</ClCompile>
<Lib />
<Lib>
<TreatLibWarningAsErrors>true</TreatLibWarningAsErrors>
</Lib>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\ApplyPatch.cpp" />
<ClCompile Include="AutopatcherPostgreRepository.cpp" />
<ClCompile Include="..\..\PostgreSQLInterface\PostgreSQLInterface.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\ApplyPatch.h" />
<ClInclude Include="AutopatcherPostgreRepository.h" />
<ClInclude Include="..\..\PostgreSQLInterface\PostgreSQLInterface.h" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Lib\LibStatic\LibStatic.vcxproj">
<Project>{6533bdae-0f0c-45e4-8fe7-add0f37fe063}</Project>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\ApplyPatch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="AutopatcherPostgreRepository.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\PostgreSQLInterface\PostgreSQLInterface.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\ApplyPatch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="AutopatcherPostgreRepository.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\PostgreSQLInterface\PostgreSQLInterface.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@ -0,0 +1,27 @@
#
# This file was taken from RakNet 4.082.
# Please see licenses/RakNet license.txt for the underlying license and related copyright.
#
#
# Modified work: Copyright (c) 2017-2019, 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.
#
project(AutopatcherPostgreRepository)
FINDPOSTGRE()
IF(WIN32 AND NOT UNIX)
FILE(GLOB ALL_HEADER_SRCS *.h ${PostgreSQLInterface_SOURCE_DIR}/PostgreSQLInterface.h ${Autopatcher_SOURCE_DIR}/ApplyPatch.h ${Autopatcher_SOURCE_DIR}/CreatePatch.h)
FILE(GLOB ALL_CPP_SRCS *.cpp ${PostgreSQLInterface_SOURCE_DIR}/PostgreSQLInterface.cpp ${Autopatcher_SOURCE_DIR}/ApplyPatch.cpp ${Autopatcher_SOURCE_DIR}/CreatePatch.cpp)
include_directories(${SLIKENET_HEADER_FILES} ./ ${PostgreSQLInterface_SOURCE_DIR} ${Autopatcher_SOURCE_DIR} ${POSTGRESQL_INCLUDE_DIR} ${BZip2_SOURCE_DIR})
add_library(AutopatcherPostgreRepository STATIC ${ALL_CPP_SRCS} ${ALL_HEADER_SRCS})
target_link_libraries (AutopatcherPostgreRepository ${SLIKENET_COMMON_LIBS} ${POSTGRESQL_LIBRARIES})
VSUBFOLDER(AutopatcherPostgreRepository "Samples/AutoPatcher/Server/PostgreSQL")
ELSE(WIN32 AND NOT UNIX)
FILE(GLOB ALL_HEADER_SRCS *.h)
FILE(GLOB ALL_CPP_SRCS *.cpp)
include_directories(${SLIKENET_HEADER_FILES} ./ ${PostgreSQLInterface_SOURCE_DIR} ${Autopatcher_SOURCE_DIR} ${POSTGRESQL_INCLUDE_DIR})
add_library(AutopatcherPostgreRepository STATIC ${ALL_CPP_SRCS} ${ALL_HEADER_SRCS})
target_link_libraries (AutopatcherPostgreRepository ${SLIKENET_COMMON_LIBS} LibAutopatcher LibPostgreSQLInterface ${POSTGRESQL_LIBRARIES})
ENDIF(WIN32 AND NOT UNIX)

View File

@ -0,0 +1,884 @@
/*
* 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) 2016-2020, 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.
*/
/// \file
/// \brief The server plugin for the autopatcher. Must be running for the client to get patches.
#include "AutopatcherServer.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/AutopatcherRepositoryInterface.h"
#include "slikenet/assert.h"
#include "slikenet/AutopatcherPatchContext.h"
#include <stdio.h>
#include <time.h>
using namespace SLNet;
const static unsigned HASH_LENGTH=4;
void AutopatcherServerLoadNotifier_Printf::OnQueueUpdate(SystemAddress remoteSystem, AutopatcherServerLoadNotifier::RequestType requestType, AutopatcherServerLoadNotifier::QueueOperation queueOperation, AutopatcherServerLoadNotifier::AutopatcherState *autopatcherState)
{
char *operationString;
char *requestTypeString;
char systemAddressString[32];
remoteSystem.ToString(true, systemAddressString, static_cast<size_t>(32));
if (requestType==ASUMC_GET_CHANGELIST)
requestTypeString="GetChangelist";
else
requestTypeString="GetPatch";
if (queueOperation==QO_WAS_ADDED)
operationString="added";
else if (queueOperation==QO_POPPED_ONTO_TO_PROCESSING_THREAD)
operationString="processing";
else // otherwise queueOperation == QO_WAS_ABORTED
operationString="aborted";
printf("%s %s %s. %i queued. %i working.\n", systemAddressString, requestTypeString, operationString, autopatcherState->requestsQueued, autopatcherState->requestsWorking);
}
void AutopatcherServerLoadNotifier_Printf::OnGetChangelistCompleted(
SystemAddress remoteSystem,
AutopatcherServerLoadNotifier::GetChangelistResult getChangelistResult,
AutopatcherServerLoadNotifier::AutopatcherState *autopatcherState)
{
char systemAddressString[32];
remoteSystem.ToString(true, systemAddressString, static_cast<size_t>(32));
char *changelistString;
if (getChangelistResult==GCR_DELETE_FILES)
changelistString="Delete files";
else if (getChangelistResult==GCR_ADD_FILES)
changelistString="Add files";
else if (getChangelistResult==GCR_ADD_AND_DELETE_FILES)
changelistString="Add and delete files";
else if (getChangelistResult==GCR_NOTHING_TO_DO)
changelistString="No files in changelist";
else // otherwise getChangelistResult == GCR_REPOSITORY_ERROR
changelistString="Repository error";
printf("%s GetChangelist complete. %s. %i queued. %i working.\n", systemAddressString, changelistString, autopatcherState->requestsQueued, autopatcherState->requestsWorking);
}
void AutopatcherServerLoadNotifier_Printf::OnGetPatchCompleted(SystemAddress remoteSystem, AutopatcherServerLoadNotifier::PatchResult patchResult, AutopatcherServerLoadNotifier::AutopatcherState *autopatcherState)
{
char systemAddressString[32];
remoteSystem.ToString(true, systemAddressString, static_cast<size_t>(32));
char *patchResultString;
if (patchResult==PR_NO_FILES_NEEDED_PATCHING)
patchResultString="No files needed patching";
else if (patchResult==PR_REPOSITORY_ERROR)
patchResultString="Repository error";
else if (patchResult==PR_DISALLOWED_DOWNLOADING_ORIGINAL_FILES)
patchResultString="Disallowed downloading original files";
else if (patchResult==PR_PATCHES_WERE_SENT)
patchResultString="Files pushed for patching";
else if (patchResult==PR_ABORTED_FROM_INPUT_THREAD)
patchResultString="Aborted from input thread";
else // otherwise patchResult == PR_ABORTED_FROM_DOWNLOAD_THREAD
patchResultString="Aborted from download thread";
printf("%s GetPatch complete. %s. %i queued. %i working.\n", systemAddressString, patchResultString, autopatcherState->requestsQueued, autopatcherState->requestsWorking);
}
AutopatcherServer::AutopatcherServer()
{
fileListTransfer=0;
priority=HIGH_PRIORITY;
orderingChannel=0;
// repository=0;
maxConcurrentUsers=0;
loadNotifier=0;
cache_minTime=0;
cache_maxTime=0;
cacheLoaded=false;
allowDownloadOfOriginalUnmodifiedFiles=true;
}
AutopatcherServer::~AutopatcherServer()
{
Clear();
}
void AutopatcherServer::SetUploadSendParameters(PacketPriority _priority, char _orderingChannel)
{
priority=_priority;
orderingChannel=_orderingChannel;
}
void AutopatcherServer::SetFileListTransferPlugin(FileListTransfer *flt)
{
if (fileListTransfer)
fileListTransfer->RemoveCallback(this);
fileListTransfer=flt;
if (fileListTransfer)
fileListTransfer->AddCallback(this);
}
void AutopatcherServer::StartThreads(int numThreads, int numSQLConnections, AutopatcherRepositoryInterface **sqlConnectionPtrArray)
{
RakAssert(numSQLConnections >= numThreads);
connectionPoolMutex.Lock();
for (int i=0; i < numSQLConnections; i++)
{
// Test the pointers passed, in case the user incorrectly casted an array of a different type
sqlConnectionPtrArray[i]->GetLastError();
connectionPool.Push(sqlConnectionPtrArray[i],_FILE_AND_LINE_);
}
connectionPoolMutex.Unlock();
threadPool.SetThreadDataInterface(this,0);
threadPool.StartThreads(numThreads, 0);
}
void AutopatcherServer::CacheMostRecentPatch(const char *applicationName)
{
if (connectionPool.Size()>0)
{
if (applicationName)
cache_appName=applicationName;
else
cache_appName.Clear();
cache_patchedFiles.Clear();
cache_addedFiles.Clear();
cache_deletedFiles.Clear();
cache_addedOrModifiedFileHashes.Clear();
cache_minTime=0;
cache_maxTime=0;
cacheLoaded = connectionPool[0]->GetMostRecentChangelistWithPatches(cache_appName, &cache_patchedFiles, &cache_addedFiles, &cache_addedOrModifiedFileHashes, &cache_deletedFiles, &cache_minTime, &cache_maxTime);
if (cacheLoaded==false)
{
printf("Warning: Cache not loaded. This is OK if no patch was ever saved.\n");
}
}
}
void AutopatcherServer::OnAttach(void)
{
}
void AutopatcherServer::OnDetach(void)
{
Clear();
}
void AutopatcherServer::Update(void)
{
while (PatchingUserLimitReached()==false && userRequestWaitingQueue.Size()>0)
{
Packet *packet = PopOffWaitingQueue();
switch (packet->data[0])
{
case ID_AUTOPATCHER_GET_CHANGELIST_SINCE_DATE:
OnGetChangelistSinceDateInt(packet);
break;
// Client sends ID_AUTOPATCHER_GET_PATCH with files that they have different or missing
case ID_AUTOPATCHER_GET_PATCH:
OnGetPatchInt(packet);
break;
}
DeallocPacketUnified(packet);
}
}
PluginReceiveResult AutopatcherServer::OnReceive(Packet *packet)
{
switch (packet->data[0])
{
case ID_AUTOPATCHER_GET_CHANGELIST_SINCE_DATE:
return OnGetChangelistSinceDate(packet);
case ID_AUTOPATCHER_GET_PATCH:
return OnGetPatch(packet);
}
return RR_CONTINUE_PROCESSING;
}
void AutopatcherServer::OnShutdown(void)
{
Clear();
}
void AutopatcherServer::Clear(void)
{
// Clear the waiting input and output from the thread pool.
unsigned i;
threadPool.StopThreads();
for (i=0; i < threadPool.InputSize(); i++)
{
if (DecrementPatchingUserCount(threadPool.GetInputAtIndex(i).systemAddress))
CallPatchCompleteCallback(threadPool.GetInputAtIndex(i).systemAddress, AutopatcherServerLoadNotifier::PR_ABORTED_FROM_INPUT_THREAD);
SLNet::OP_DELETE(threadPool.GetInputAtIndex(i).clientList, _FILE_AND_LINE_);
}
threadPool.ClearInput();
for (i=0; i < threadPool.OutputSize(); i++)
{
SLNet::OP_DELETE(threadPool.GetOutputAtIndex(i)->patchList, _FILE_AND_LINE_);
SLNet::OP_DELETE(threadPool.GetOutputAtIndex(i)->deletedFiles, _FILE_AND_LINE_);
SLNet::OP_DELETE(threadPool.GetOutputAtIndex(i)->addedOrModifiedFilesWithHashData, _FILE_AND_LINE_);
}
threadPool.ClearOutput();
while (userRequestWaitingQueue.Size())
DeallocPacketUnified(AbortOffWaitingQueue());
patchingUsers.Clear(true, _FILE_AND_LINE_);
}
void AutopatcherServer::OnStartup(RakPeerInterface *peer)
{
// unused parameters
(void)peer;
}
void AutopatcherServer::OnClosedConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason )
{
// unused parameters
(void)rakNetGUID;
(void)lostConnectionReason;
RemoveFromThreadPool(systemAddress);
unsigned int i=0;
patchingUsersMutex.Lock();
while (i < patchingUsers.Size())
{
if (patchingUsers[i]==systemAddress)
patchingUsers.RemoveAtIndexFast(i);
else
i++;
}
patchingUsersMutex.Unlock();
i=0;
while (i < userRequestWaitingQueue.Size())
{
if (userRequestWaitingQueue[i]->systemAddress==systemAddress)
userRequestWaitingQueue.RemoveAtIndex(i);
else
i++;
}
}
void AutopatcherServer::RemoveFromThreadPool(SystemAddress systemAddress)
{
unsigned int i = 0;
threadPool.LockInput();
while (i < threadPool.InputSize())
{
if (threadPool.GetInputAtIndex(i).systemAddress==systemAddress)
{
if (DecrementPatchingUserCount(systemAddress))
CallPatchCompleteCallback(threadPool.GetInputAtIndex(i).systemAddress, AutopatcherServerLoadNotifier::PR_ABORTED_FROM_INPUT_THREAD);
SLNet::OP_DELETE(threadPool.GetInputAtIndex(i).clientList, _FILE_AND_LINE_);
threadPool.RemoveInputAtIndex(i);
}
else
i++;
}
threadPool.UnlockInput();
i=0;
threadPool.LockOutput();
while (i < threadPool.OutputSize())
{
if (threadPool.GetOutputAtIndex(i)->systemAddress==systemAddress)
{
SLNet::OP_DELETE(threadPool.GetOutputAtIndex(i)->patchList, _FILE_AND_LINE_);
SLNet::OP_DELETE(threadPool.GetOutputAtIndex(i)->deletedFiles, _FILE_AND_LINE_);
SLNet::OP_DELETE(threadPool.GetOutputAtIndex(i)->addedOrModifiedFilesWithHashData, _FILE_AND_LINE_);
threadPool.RemoveOutputAtIndex(i);
}
else
i++;
}
threadPool.UnlockOutput();
}
namespace SLNet
{
AutopatcherServer::ResultTypeAndBitstream* GetChangelistSinceDateCB(AutopatcherServer::ThreadData threadData, bool *returnOutput, void* perThreadData)
{
AutopatcherRepositoryInterface *repository = (AutopatcherRepositoryInterface*)perThreadData;
FileList addedOrModifiedFilesWithHashData, deletedFiles;
AutopatcherServer *server = threadData.server;
//AutopatcherServer::ResultTypeAndBitstream *rtab = SLNet::OP_NEW<AutopatcherServer::ResultTypeAndBitstream>( _FILE_AND_LINE_ );
AutopatcherServer::ResultTypeAndBitstream rtab;
rtab.systemAddress=threadData.systemAddress;
// rtab.deletedFiles=SLNet::OP_NEW<FileList>( _FILE_AND_LINE_ );
// rtab.addedFiles=SLNet::OP_NEW<FileList>( _FILE_AND_LINE_ );
rtab.deletedFiles=&deletedFiles;
rtab.addedOrModifiedFilesWithHashData=&addedOrModifiedFilesWithHashData;
// Query the database for a changelist since this date
RakAssert(server);
//if (server->repository->GetChangelistSinceDate(threadData.applicationName.C_String(), rtab.addedFiles, rtab.deletedFiles, threadData.lastUpdateDate.C_String(), currentDate))
if (repository->GetChangelistSinceDate(threadData.applicationName.C_String(), rtab.addedOrModifiedFilesWithHashData, rtab.deletedFiles, threadData.lastUpdateDate))
{
rtab.resultCode=1;
}
else
{
rtab.resultCode=0;
}
rtab.operation=AutopatcherServer::ResultTypeAndBitstream::GET_CHANGELIST_SINCE_DATE;
rtab.currentDate=(double) time(nullptr);
// *returnOutput=true;
// return rtab;
if (rtab.resultCode==1)
{
if (rtab.deletedFiles->fileList.Size())
{
rtab.bitStream1.Write((unsigned char) ID_AUTOPATCHER_DELETION_LIST);
rtab.deletedFiles->Serialize(&rtab.bitStream1);
}
if (rtab.addedOrModifiedFilesWithHashData->fileList.Size())
{
rtab.bitStream2.Write((unsigned char) ID_AUTOPATCHER_CREATION_LIST);
rtab.addedOrModifiedFilesWithHashData->Serialize(&rtab.bitStream2);
rtab.bitStream2.Write(rtab.currentDate);
rtab.bitStream2.WriteCasted<double>(0);
rtab.addedOrModifiedFilesWithHashData->Clear();
}
else
{
rtab.bitStream2.Write((unsigned char) ID_AUTOPATCHER_FINISHED);
rtab.bitStream2.Write(rtab.currentDate);
}
}
else
{
rtab.bitStream2.Write((unsigned char) ID_AUTOPATCHER_REPOSITORY_FATAL_ERROR);
StringCompressor::Instance()->EncodeString(repository->GetLastError(), 256, &rtab.bitStream2);
}
// SLNet::OP_DELETE(rtab.deletedFiles, _FILE_AND_LINE_);
// SLNet::OP_DELETE(rtab.addedFiles, _FILE_AND_LINE_);
*returnOutput=false;
if (server->DecrementPatchingUserCount(rtab.systemAddress))
{
if (rtab.bitStream1.GetNumberOfBitsUsed()>0)
server->SendUnified(&(rtab.bitStream1), server->priority, RELIABLE_ORDERED, server->orderingChannel, rtab.systemAddress, false);
if (rtab.bitStream2.GetNumberOfBitsUsed()>0)
server->SendUnified(&(rtab.bitStream2), server->priority, RELIABLE_ORDERED, server->orderingChannel, rtab.systemAddress, false);
if (server->loadNotifier)
{
AutopatcherServerLoadNotifier::AutopatcherState autopatcherState;
autopatcherState.requestsQueued=server->userRequestWaitingQueue.Size();
autopatcherState.requestsWorking=server->patchingUsers.Size();
AutopatcherServerLoadNotifier::GetChangelistResult getChangelistResult;
if (rtab.resultCode!=1)
getChangelistResult=AutopatcherServerLoadNotifier::GCR_REPOSITORY_ERROR;
else if (rtab.deletedFiles->fileList.Size()==0 && rtab.addedOrModifiedFilesWithHashData->fileList.Size()==0)
getChangelistResult=AutopatcherServerLoadNotifier::GCR_NOTHING_TO_DO;
else if (rtab.deletedFiles->fileList.Size()==0)
getChangelistResult=AutopatcherServerLoadNotifier::GCR_ADD_FILES;
else if (rtab.addedOrModifiedFilesWithHashData->fileList.Size()==0)
getChangelistResult=AutopatcherServerLoadNotifier::GCR_DELETE_FILES;
else
getChangelistResult=AutopatcherServerLoadNotifier::GCR_ADD_AND_DELETE_FILES;
server->loadNotifier->OnGetChangelistCompleted(rtab.systemAddress, getChangelistResult, &autopatcherState);
}
}
return 0;
}
}
PluginReceiveResult AutopatcherServer::OnGetChangelistSinceDate(Packet *packet)
{
SLNet::BitStream inBitStream(packet->data, packet->length, false);
ThreadData threadData;
threadData.clientList=0;
inBitStream.IgnoreBits(8);
inBitStream.ReadCompressed(threadData.applicationName);
inBitStream.Read(threadData.lastUpdateDate);
if (cacheLoaded && threadData.lastUpdateDate!=0 && threadData.applicationName==cache_appName)
{
SLNet::BitStream bitStream1;
SLNet::BitStream bitStream2;
double currentDate=(double) time(nullptr);
if (cache_maxTime!=0 && threadData.lastUpdateDate>cache_maxTime)
{
bitStream2.Write((unsigned char) ID_AUTOPATCHER_FINISHED);
bitStream2.Write(currentDate);
SendUnified(&bitStream2, priority, RELIABLE_ORDERED,orderingChannel, packet->systemAddress, false);
return RR_STOP_PROCESSING_AND_DEALLOCATE;
}
// Check in-memory cache, use if possible rather than accessing database
if (cache_minTime!=0 && threadData.lastUpdateDate>cache_minTime)
{
if (cache_deletedFiles.fileList.Size())
{
bitStream1.Write((unsigned char) ID_AUTOPATCHER_DELETION_LIST);
cache_deletedFiles.Serialize(&bitStream1);
SendUnified(&bitStream1, priority, RELIABLE_ORDERED,orderingChannel, packet->systemAddress, false);
}
if (cache_addedOrModifiedFileHashes.fileList.Size())
{
bitStream2.Write((unsigned char) ID_AUTOPATCHER_CREATION_LIST);
cache_addedOrModifiedFileHashes.Serialize(&bitStream2);
bitStream2.Write(currentDate);
bitStream2.Write(threadData.lastUpdateDate);
}
else
{
bitStream2.Write((unsigned char) ID_AUTOPATCHER_FINISHED);
bitStream2.Write(currentDate);
}
SendUnified(&bitStream2, priority, RELIABLE_ORDERED,orderingChannel, packet->systemAddress, false);
return RR_STOP_PROCESSING_AND_DEALLOCATE;
}
}
if (PatchingUserLimitReached())
{
AddToWaitingQueue(packet);
return RR_STOP_PROCESSING;
}
OnGetChangelistSinceDateInt(packet);
return RR_STOP_PROCESSING_AND_DEALLOCATE;
}
void AutopatcherServer::OnGetChangelistSinceDateInt(Packet *packet)
{
SLNet::BitStream inBitStream(packet->data, packet->length, false);
ThreadData threadData;
threadData.clientList=0;
inBitStream.IgnoreBits(8);
inBitStream.ReadCompressed(threadData.applicationName);
inBitStream.Read(threadData.lastUpdateDate);
if (IncrementPatchingUserCount(packet->systemAddress))
{
CallPacketCallback(packet, AutopatcherServerLoadNotifier::QO_POPPED_ONTO_TO_PROCESSING_THREAD);
threadData.server=this;
threadData.systemAddress=packet->systemAddress;
threadPool.AddInput(GetChangelistSinceDateCB, threadData);
}
}
namespace SLNet {
AutopatcherServer::ResultTypeAndBitstream* GetPatchCB(AutopatcherServer::ThreadData threadData, bool *returnOutput, void* perThreadData)
{
AutopatcherServer *server = threadData.server;
AutopatcherRepositoryInterface *repository = (AutopatcherRepositoryInterface*)perThreadData;
// AutopatcherServer::ResultTypeAndBitstream *rtab = SLNet::OP_NEW<AutopatcherServer::ResultTypeAndBitstream>( _FILE_AND_LINE_ );
AutopatcherServer::ResultTypeAndBitstream rtab;
rtab.systemAddress=threadData.systemAddress;
FileList fileList;
// rtab.patchList=SLNet::OP_NEW<FileList>( _FILE_AND_LINE_ );
rtab.patchList=&fileList;
RakAssert(server);
// RakAssert(server->repository);
// if (server->repository->GetPatches(threadData.applicationName.C_String(), threadData.clientList, rtab.patchList, currentDate))
rtab.resultCode = repository->GetPatches(threadData.applicationName.C_String(), threadData.clientList, server->allowDownloadOfOriginalUnmodifiedFiles, rtab.patchList);
rtab.operation=AutopatcherServer::ResultTypeAndBitstream::GET_PATCH;
rtab.setId=threadData.setId;
rtab.currentDate=(double) time(nullptr);
SLNet::OP_DELETE(threadData.clientList, _FILE_AND_LINE_);
if (rtab.resultCode==1)
{
if (rtab.patchList->fileList.Size())
{
//server->fileListTransfer->Send(rtab.patchList, 0, rtab.systemAddress, rtab.setId, server->priority, server->orderingChannel, false, server->repository);
server->fileListTransfer->Send(rtab.patchList, 0, rtab.systemAddress, rtab.setId, server->priority, server->orderingChannel, repository, repository->GetIncrementalReadChunkSize());
}
else
{
// No files needed to send
if (server->DecrementPatchingUserCount(rtab.systemAddress))
server->CallPatchCompleteCallback(rtab.systemAddress, AutopatcherServerLoadNotifier::PR_NO_FILES_NEEDED_PATCHING);
}
rtab.bitStream1.Write((unsigned char) ID_AUTOPATCHER_FINISHED_INTERNAL);
rtab.bitStream1.Write(rtab.currentDate);
}
else
{
AutopatcherServerLoadNotifier::PatchResult pr;
if (rtab.resultCode==0)
{
rtab.bitStream1.Write((unsigned char) ID_AUTOPATCHER_REPOSITORY_FATAL_ERROR);
StringCompressor::Instance()->EncodeString(repository->GetLastError(), 256, &rtab.bitStream1);
pr = AutopatcherServerLoadNotifier::PR_REPOSITORY_ERROR;
}
else
{
rtab.bitStream1.Write((unsigned char) ID_AUTOPATCHER_CANNOT_DOWNLOAD_ORIGINAL_UNMODIFIED_FILES);
pr = AutopatcherServerLoadNotifier::PR_DISALLOWED_DOWNLOADING_ORIGINAL_FILES;
}
if (server->DecrementPatchingUserCount(rtab.systemAddress))
{
server->CallPatchCompleteCallback(rtab.systemAddress, pr);
}
else
{
*returnOutput=false;
return 0;
}
}
*returnOutput=false;
if (rtab.bitStream1.GetNumberOfBitsUsed()>0)
server->SendUnified(&(rtab.bitStream1), server->priority, RELIABLE_ORDERED, server->orderingChannel, rtab.systemAddress, false);
if (rtab.bitStream2.GetNumberOfBitsUsed()>0)
server->SendUnified(&(rtab.bitStream2), server->priority, RELIABLE_ORDERED, server->orderingChannel, rtab.systemAddress, false);
// 12/1/2010 This doesn't scale well. Changing to allocating a connection object per request
/*
// Wait for repository to finish
// This is so that the same sql connection is not used between two different plugins, which causes thrashing and bad performance
// Plus if fileListTransfer uses multiple threads, this will keep this thread and the fileListTransfer thread from using the same connection at the same time
// PostgreSQL possibly MySQL are not threadsafe for multiple threads on the same connection
int pendingFiles = server->fileListTransfer->GetPendingFilesToAddress(rtab.systemAddress);
while (pendingFiles>0)
{
RakSleep(pendingFiles*10);
pendingFiles = server->fileListTransfer->GetPendingFilesToAddress(rtab.systemAddress);
}
*/
// *returnOutput=true;
// return rtab;
return 0;
}
}
PluginReceiveResult AutopatcherServer::OnGetPatch(Packet *packet)
{
SLNet::BitStream inBitStream(packet->data, packet->length, false);
ThreadData threadData;
inBitStream.IgnoreBits(8);
inBitStream.Read(threadData.setId);
double lastUpdateDate;
inBitStream.Read(lastUpdateDate);
inBitStream.ReadCompressed(threadData.applicationName);
threadData.clientList=0;
// Check in-memory cache, use if possible rather than accessing database
if (threadData.applicationName==cache_appName && lastUpdateDate!=0 && cacheLoaded && cache_minTime!=0 && lastUpdateDate>cache_minTime)
{
threadData.systemAddress=packet->systemAddress;
threadData.server=this;
threadData.clientList= SLNet::OP_NEW<FileList>( _FILE_AND_LINE_ );
if (threadData.clientList->Deserialize(&inBitStream)==false)
{
SLNet::OP_DELETE(threadData.clientList, _FILE_AND_LINE_);
return RR_STOP_PROCESSING_AND_DEALLOCATE;
}
if (threadData.clientList->fileList.Size()==0)
{
RakAssert(0);
SLNet::OP_DELETE(threadData.clientList, _FILE_AND_LINE_);
return RR_STOP_PROCESSING_AND_DEALLOCATE;
}
char *userHash;
SLNet::RakString userFilename;
FileList patchList;
bool cacheUpdateFailed=false;
unsigned int i,j;
// FileList is the list of all files missing or changed as determined by the client
for (i=0; i < threadData.clientList->fileList.Size(); i++)
{
userHash=threadData.clientList->fileList[i].data;
userFilename=threadData.clientList->fileList[i].filename;
if (userHash)
{
// If the user has a hash, check for this file in cache_patchedFiles. If not found, or hash is wrong, use DB
if (threadData.clientList->fileList[i].dataLengthBytes!=HASH_LENGTH)
{
SLNet::OP_DELETE(threadData.clientList, _FILE_AND_LINE_);
return RR_STOP_PROCESSING_AND_DEALLOCATE;
}
for (j=0; j < cache_patchedFiles.fileList.Size(); j++)
{
if (userFilename == cache_patchedFiles.fileList[j].filename)
{
if (memcmp(cache_patchedFiles.fileList[j].data, userHash, HASH_LENGTH)==0)
{
// Send patch
RakAssert(cache_patchedFiles.fileList[j].context.op==PC_HASH_2_WITH_PATCH);
patchList.AddFile(userFilename,userFilename, 0, cache_patchedFiles.fileList[j].dataLengthBytes, cache_patchedFiles.fileList[j].fileLengthBytes, cache_patchedFiles.fileList[j].context, true, false);
}
else
{
// Bad hash
cacheUpdateFailed=true;
}
break;
}
}
if (j==cache_patchedFiles.fileList.Size())
{
// Didn't find the patch even though the client has an older version of the file
cacheUpdateFailed=true;
}
}
else
{
// If the user does not have a hash, check for this file in cache_addedFiles. If not found, use DB
for (j=0; j < cache_addedFiles.fileList.Size(); j++)
{
if (userFilename == cache_addedFiles.fileList[j].filename)
{
// Send added file
patchList.AddFile(userFilename,userFilename, 0, cache_addedFiles.fileList[j].dataLengthBytes, cache_addedFiles.fileList[j].fileLengthBytes, cache_addedFiles.fileList[j].context, true, false);
break;
}
}
if (j==cache_addedFiles.fileList.Size())
{
// Didn't find the file in the cache even though the client asked for it
cacheUpdateFailed=true;
}
}
if (cacheUpdateFailed==true)
{
// Failure to find file in cache
// Will fall to use database
patchList.Clear();
break;
}
}
if (patchList.fileList.Size()>0)
{
if (IncrementPatchingUserCount(packet->systemAddress))
{
fileListTransfer->Send(&patchList, 0, packet->systemAddress, threadData.setId, priority, orderingChannel, this, 262144*4*4);
SLNet::BitStream bitStream1;
bitStream1.Write((unsigned char) ID_AUTOPATCHER_FINISHED_INTERNAL);
double t =(double) time(nullptr);
bitStream1.Write(t);
SendUnified(&bitStream1, priority, RELIABLE_ORDERED, orderingChannel, packet->systemAddress, false);
SLNet::OP_DELETE(threadData.clientList, _FILE_AND_LINE_);
return RR_STOP_PROCESSING_AND_DEALLOCATE;
}
}
}
SLNet::OP_DELETE(threadData.clientList, _FILE_AND_LINE_);
if (PatchingUserLimitReached())
{
AddToWaitingQueue(packet);
return RR_STOP_PROCESSING;
}
OnGetPatchInt(packet);
return RR_STOP_PROCESSING_AND_DEALLOCATE;
}
void AutopatcherServer::OnGetPatchInt(Packet *packet)
{
SLNet::BitStream inBitStream(packet->data, packet->length, false);
ThreadData threadData;
inBitStream.IgnoreBits(8);
inBitStream.Read(threadData.setId);
double lastUpdateDate;
inBitStream.Read(lastUpdateDate);
inBitStream.ReadCompressed(threadData.applicationName);
threadData.systemAddress=packet->systemAddress;
threadData.server=this;
threadData.clientList= SLNet::OP_NEW<FileList>( _FILE_AND_LINE_ );
if (threadData.clientList->Deserialize(&inBitStream)==false)
{
SLNet::OP_DELETE(threadData.clientList, _FILE_AND_LINE_);
return;
}
if (threadData.clientList->fileList.Size()==0)
{
RakAssert(0);
SLNet::OP_DELETE(threadData.clientList, _FILE_AND_LINE_);
return;
}
if (IncrementPatchingUserCount(packet->systemAddress))
CallPacketCallback(packet, AutopatcherServerLoadNotifier::QO_POPPED_ONTO_TO_PROCESSING_THREAD);
threadPool.AddInput(GetPatchCB, threadData);
}
void* AutopatcherServer::PerThreadFactory(void *context)
{
(void)context;
AutopatcherRepositoryInterface* p;
connectionPoolMutex.Lock();
p=connectionPool.Pop();
connectionPoolMutex.Unlock();
return p;
}
void AutopatcherServer::PerThreadDestructor(void* factoryResult, void *context)
{
(void)context;
(void)factoryResult;
}
void AutopatcherServer::OnFilePushesComplete( SystemAddress systemAddress, unsigned short setID )
{
// unused parameters
(void)setID;
if (DecrementPatchingUserCount(systemAddress))
CallPatchCompleteCallback(systemAddress, AutopatcherServerLoadNotifier::PR_PATCHES_WERE_SENT);
}
void AutopatcherServer::OnSendAborted( SystemAddress systemAddress )
{
if (DecrementPatchingUserCount(systemAddress))
CallPatchCompleteCallback(systemAddress, AutopatcherServerLoadNotifier::PR_ABORTED_FROM_DOWNLOAD_THREAD);
}
bool AutopatcherServer::IncrementPatchingUserCount(SystemAddress sa)
{
// A system address may exist more than once in patchingUsers
patchingUsersMutex.Lock();
patchingUsers.Insert(sa, _FILE_AND_LINE_);
patchingUsersMutex.Unlock();
return true;
}
bool AutopatcherServer::DecrementPatchingUserCount(SystemAddress sa)
{
unsigned int i;
patchingUsersMutex.Lock();
for (i=0; i < patchingUsers.Size(); i++)
{
if (patchingUsers[i]==sa)
{
patchingUsers.RemoveAtIndexFast(i);
patchingUsersMutex.Unlock();
return true;
}
}
patchingUsersMutex.Unlock();
return false;
}
bool AutopatcherServer::PatchingUserLimitReached(void) const
{
if (maxConcurrentUsers==0)
return false;
return patchingUsers.Size()>=maxConcurrentUsers;
}
void AutopatcherServer::SetMaxConurrentUsers(unsigned int _maxConcurrentUsers)
{
maxConcurrentUsers=_maxConcurrentUsers;
}
unsigned int AutopatcherServer::GetMaxConurrentUsers(void) const
{
return maxConcurrentUsers;
}
void AutopatcherServer::CallPacketCallback(Packet *packet, AutopatcherServerLoadNotifier::QueueOperation queueOperation)
{
if (loadNotifier)
{
AutopatcherServerLoadNotifier::AutopatcherState autopatcherState;
autopatcherState.requestsQueued=userRequestWaitingQueue.Size();
autopatcherState.requestsWorking=patchingUsers.Size();
AutopatcherServerLoadNotifier::RequestType requestType;
if (packet->data[0]==ID_AUTOPATCHER_GET_CHANGELIST_SINCE_DATE)
requestType=AutopatcherServerLoadNotifier::ASUMC_GET_CHANGELIST;
else
requestType=AutopatcherServerLoadNotifier::ASUMC_GET_PATCH;
loadNotifier->OnQueueUpdate(packet->systemAddress, requestType, queueOperation, &autopatcherState);
}
}
void AutopatcherServer::CallPatchCompleteCallback(const SystemAddress &systemAddress, AutopatcherServerLoadNotifier::PatchResult patchResult)
{
if (loadNotifier)
{
AutopatcherServerLoadNotifier::AutopatcherState autopatcherState;
autopatcherState.requestsQueued=userRequestWaitingQueue.Size();
autopatcherState.requestsWorking=patchingUsers.Size();
loadNotifier->OnGetPatchCompleted(systemAddress, patchResult, &autopatcherState);
}
}
void AutopatcherServer::AddToWaitingQueue(Packet *packet)
{
userRequestWaitingQueue.Push(packet, _FILE_AND_LINE_);
CallPacketCallback(packet, AutopatcherServerLoadNotifier::QO_WAS_ADDED);
}
Packet *AutopatcherServer::AbortOffWaitingQueue(void)
{
Packet *packet = userRequestWaitingQueue.Pop();
CallPacketCallback(packet,AutopatcherServerLoadNotifier::QO_WAS_ABORTED);
return packet;
}
Packet *AutopatcherServer::PopOffWaitingQueue(void)
{
return userRequestWaitingQueue.Pop();;
}
void AutopatcherServer::SetLoadManagementCallback(AutopatcherServerLoadNotifier *asumc)
{
loadNotifier=asumc;
}
void AutopatcherServer::SetAllowDownloadOfOriginalUnmodifiedFiles(bool allow)
{
allowDownloadOfOriginalUnmodifiedFiles = allow;
}
unsigned int AutopatcherServer::GetFilePart( const char *filename, unsigned int startReadBytes, unsigned int numBytesToRead, void *preallocatedDestination, FileListNodeContext context)
{
// unused parameters
(void)filename;
/*
int offset;
if (context.op==PC_HASH_1_WITH_PATCH)
offset=HASH_LENGTH;
else if (context.op==PC_HASH_2_WITH_PATCH)
offset=HASH_LENGTH*2;
else
offset=0;
int bytesToRead;
if (startReadBytes + numBytesToRead > context.dataLength-offset)
bytesToRead=(context.dataLength-offset)-startReadBytes;
else
bytesToRead=numBytesToRead;
memcpy(preallocatedDestination, ((char*)context.dataPtr)+offset, bytesToRead);
*/
int bytesToRead;
if (startReadBytes + numBytesToRead > context.dataLength)
bytesToRead=(context.dataLength)-startReadBytes;
else
bytesToRead=numBytesToRead;
memcpy(preallocatedDestination, context.dataPtr, bytesToRead);
return bytesToRead;
}

View File

@ -0,0 +1,325 @@
/*
* 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.
*/
/// \file
/// \brief The server plugin for the autopatcher. Must be running for the client to get patches.
// TODO - bsdiff doesn't work for files above 100 megabytes.
// See http://xdelta.org/
// XDelta is GPL 2, however I could run that as a separate EXE and invoke to only transmit the delta file.
// See http://pocketsoft.com/rtpatch.html
// See use rdiff instead of bsdiff, or perhaps librsync
// See https://code.google.com/p/open-vcdiff/
// https://code.google.com/p/open-vcdiff/wiki/HowToUseOpenVcdiff
// https://github.com/gtoubassi/femtozip/wiki/Sdch
#ifndef __AUTOPATCHER_SERVER_H
#define __AUTOPATCHER_SERVER_H
#include "slikenet/types.h"
#include "slikenet/Export.h"
#include "slikenet/PluginInterface2.h"
#include "slikenet/PacketPriority.h"
#include "slikenet/ThreadPool.h"
#include "slikenet/BitStream.h"
#include "slikenet/string.h"
#include "slikenet/FileList.h"
#include "slikenet/IncrementalReadInterface.h"
namespace SLNet
{
/// Forward declarations
class RakPeerInterface;
struct Packet;
class AutopatcherRepositoryInterface;
class FileListTransfer;
class RAK_DLL_EXPORT AutopatcherServerLoadNotifier
{
public:
/// Current queue state of the autopatcher
struct AutopatcherState
{
/// How many requests have been queued to be processed later
unsigned int requestsQueued;
/// How many requests are currently working (including downloading files).
/// This will not normally exceed AutopatcherServer::SetMaxConurrentUsers()
unsigned int requestsWorking;
};
/// The server only handles two types of requests - to get a change list since a certain date, or to get a patch
enum RequestType
{
ASUMC_GET_CHANGELIST,
ASUMC_GET_PATCH,
};
enum QueueOperation
{
QO_WAS_ADDED,
QO_WAS_ABORTED,
QO_POPPED_ONTO_TO_PROCESSING_THREAD
};
enum GetChangelistResult
{
GCR_DELETE_FILES,
GCR_ADD_FILES,
GCR_ADD_AND_DELETE_FILES,
GCR_NOTHING_TO_DO,
GCR_REPOSITORY_ERROR,
};
enum PatchResult
{
PR_NO_FILES_NEEDED_PATCHING,
PR_REPOSITORY_ERROR,
PR_DISALLOWED_DOWNLOADING_ORIGINAL_FILES,
PR_PATCHES_WERE_SENT,
PR_ABORTED_FROM_INPUT_THREAD,
PR_ABORTED_FROM_DOWNLOAD_THREAD,
};
/// The server queues have been updated
/// \param[out] remoteSystem Which system this refers to
/// \param[out] requestType Either added to / removed a changelist request, or a get patch request
/// \param[out] queueOperation The operation was added to the queue, removed from the queue to be processed, or removed because it was aborted
/// \param[out] autopatcherState Current size of the request queue, and how many requests are working
virtual void OnQueueUpdate(SystemAddress remoteSystem,
AutopatcherServerLoadNotifier::RequestType requestType,
AutopatcherServerLoadNotifier::QueueOperation queueOperation,
AutopatcherServerLoadNotifier::AutopatcherState *autopatcherState)
{
// unused parameters
(void)remoteSystem;
(void)requestType;
(void)queueOperation;
(void)autopatcherState;
}
virtual void OnGetChangelistCompleted(
SystemAddress remoteSystem,
AutopatcherServerLoadNotifier::GetChangelistResult getChangelistResult,
AutopatcherServerLoadNotifier::AutopatcherState *autopatcherState)
{
// unused parameters
(void)remoteSystem;
(void)getChangelistResult;
(void)autopatcherState;
}
/// A file transfer has completed, or was not necessary
/// \param[out] remoteSystem Which system this refers to
/// \param[out] autopatcherState Current size of the request queue, and how many requests are working
virtual void OnGetPatchCompleted(SystemAddress remoteSystem,
AutopatcherServerLoadNotifier::PatchResult patchResult,
AutopatcherServerLoadNotifier::AutopatcherState *autopatcherState)
{
// unused parameters
(void)remoteSystem;
(void)patchResult;
(void)autopatcherState;
}
};
/// \brief Sample implementation of AutopatcherServerLoadNotifier using printf
class RAK_DLL_EXPORT AutopatcherServerLoadNotifier_Printf : public AutopatcherServerLoadNotifier
{
public:
virtual void OnQueueUpdate(SystemAddress remoteSystem,
AutopatcherServerLoadNotifier::RequestType requestType,
AutopatcherServerLoadNotifier::QueueOperation queueOperation,
AutopatcherServerLoadNotifier::AutopatcherState *autopatcherState);
virtual void OnGetChangelistCompleted(
SystemAddress remoteSystem,
AutopatcherServerLoadNotifier::GetChangelistResult getChangelistResult,
AutopatcherServerLoadNotifier::AutopatcherState *autopatcherState);
virtual void OnGetPatchCompleted(SystemAddress remoteSystem,
AutopatcherServerLoadNotifier::PatchResult patchResult,
AutopatcherServerLoadNotifier::AutopatcherState *autopatcherState);
};
/// \brief The server plugin for the autopatcher. Must be running for the client to get patches.
class RAK_DLL_EXPORT AutopatcherServer : public PluginInterface2 , public ThreadDataInterface, FileListProgress, IncrementalReadInterface
{
public:
// Constructor
AutopatcherServer();
// Destructor
~AutopatcherServer();
/// DO THIS FIRST
/// Implement to start the worker threads.
/// Before this is called, no queries will be performed
/// When this is called, AllocAutopatcherRepositoryInterface will be called with \a repositoryAllocationParameters
/// The system works in three phases.
/// 1. Get change list since a given date. This uses one of the worker threads.
/// 2. If either running a full scan, or files have changed since a given date, get the list of patches. This uses one of the worker threads and does an intensive comparison of the hashes the client has vs. the files in the database
/// 3. If the total amount of data to be sent exceeds DATABASE_READ_CHUNK_SIZE, defined in the cpp file, then the database will be read from incrementally during this download phase. This uses an sql connection object, which may or may not be also in use by one of the threads.
/// If you have more sql connections than threads, this tends to prevent the same connection from being used to incrementally read files for a downloader, and to be used in a worker thread.
/// \param[in] numThreads Number of processing threads, which handles the CPU intensive tasks of generating a patch list and comparing files
/// \param[in] numSQLConnections Number of SQL connection objects passed to \a sqlConnectionPtrArray. Must be greater than or equal to numThreads
/// \param[in] sqlConnectionPtrArray List of pointers to AutopatcherRepositoryInterface. C++ note: Don't just cast a derived class array, you need to take the pointer address of each item
void StartThreads(int numThreads, int numSQLConnections, AutopatcherRepositoryInterface **sqlConnectionPtrArray);
/// Load the most recent patch in memory and keep it there
/// This can take a lot of memory, but greatly speeds up serving patches, since disk access is not incurred
/// \param[in] applicationName 0 means all, otherwise the name of the application to cache
void CacheMostRecentPatch(const char *applicationName);
/// What parameters to use for the RakPeerInterface::Send() call when uploading files.
/// \param[in] _priority See RakPeerInterface::Send()
/// \param[in] _orderingChannel See RakPeerInterface::Send()
void SetUploadSendParameters(PacketPriority _priority, char _orderingChannel);
/// This plugin has a dependency on the FileListTransfer plugin, which it uses to actually send the files.
/// So you need an instance of that plugin registered with RakPeerInterface, and a pointer to that interface should be passed here.
/// \param[in] flt A pointer to a registered instance of FileListTransfer
void SetFileListTransferPlugin(FileListTransfer *flt);
/// This is the maximum number of users the patcher will service at one time (generally about equal to the number of downloads at once)
/// If this limit is exceeded, the request packet will be put into a queue and serviced when slots are available
/// Defaults to 0 (unlimited)
/// \param[in] maxConcurrentUsers Pass 0 for unlimited, otherwise the max users to serve at once
void SetMaxConurrentUsers(unsigned int _maxConcurrentUsers);
/// \return Returns what was passed to SetMaxConurrentUsers();
unsigned int GetMaxConurrentUsers(void) const;
/// Set a callback to get notifications of when user requests are queued and processed
/// This is primarily of use to load balance the server
/// \param[in] asumc An externally allocated instance of AutopatcherServerLoadNotifier. Pass 0 to disable.
void SetLoadManagementCallback(AutopatcherServerLoadNotifier *asumc);
/// Set whether or not the client can download files that were never modified, that they do not have
/// Defaults to true
/// Set to false to disallow downloading the entire game through the autopatcher. In this case, the user must have a copy of the game through other means (such as a CD install)
/// \param[in] allow True to allow downloading original game files, false to disallow
void SetAllowDownloadOfOriginalUnmodifiedFiles(bool allow);
/// Clear buffered input and output
void Clear(void);
/// \internal For plugin handling
virtual void OnAttach(void);
/// \internal For plugin handling
virtual void OnDetach(void);;
/// \internal For plugin handling
virtual void Update(void);
/// \internal For plugin handling
virtual PluginReceiveResult OnReceive(Packet *packet);
/// \internal For plugin handling
virtual void OnShutdown(void);
/// \internal For plugin handling
virtual void OnStartup(RakPeerInterface *peer);
/// \internal For plugin handling
virtual void OnClosedConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID, PI2_LostConnectionReason lostConnectionReason );
struct ThreadData
{
AutopatcherServer *server;
SLNet::RakString applicationName;
double lastUpdateDate;
SystemAddress systemAddress;
FileList *clientList;
unsigned short setId;
};
/// \deprecated
struct ResultTypeAndBitstream
{
ResultTypeAndBitstream() {patchList=0; deletedFiles=0; addedOrModifiedFilesWithHashData=0;}
int resultType;
SystemAddress systemAddress;
SLNet::BitStream bitStream1;
SLNet::BitStream bitStream2;
FileList *patchList;
FileList *deletedFiles, *addedOrModifiedFilesWithHashData;
// bool fatalError;
int resultCode; // 1 = success, 0 = unknown error, -1 = failed allowDownloadOfOriginalUnmodifiedFiles check
unsigned short setId;
double currentDate;
enum
{
GET_CHANGELIST_SINCE_DATE,
GET_PATCH,
} operation;
};
protected:
friend AutopatcherServer::ResultTypeAndBitstream* GetChangelistSinceDateCB(AutopatcherServer::ThreadData pap, bool *returnOutput, void* perThreadData);
friend AutopatcherServer::ResultTypeAndBitstream* GetPatchCB(AutopatcherServer::ThreadData pap, bool *returnOutput, void* perThreadData);
PluginReceiveResult OnGetChangelistSinceDate(Packet *packet);
PluginReceiveResult OnGetPatch(Packet *packet);
void OnGetChangelistSinceDateInt(Packet *packet);
void OnGetPatchInt(Packet *packet);
void* PerThreadFactory(void *context);
void PerThreadDestructor(void* factoryResult, void *context);
void RemoveFromThreadPool(SystemAddress systemAddress);
virtual unsigned int GetFilePart( const char *filename, unsigned int startReadBytes, unsigned int numBytesToRead, void *preallocatedDestination, FileListNodeContext context);
//AutopatcherRepositoryInterface *repository;
FileListTransfer *fileListTransfer;
PacketPriority priority;
char orderingChannel;
// The point of the threadPool is so that SQL queries, which are blocking, happen in the thread and don't slow down the rest of the application
// The threadPool has a queue for incoming processing requests. As systems disconnect their pending requests are removed from the list.
ThreadPool<ThreadData, ResultTypeAndBitstream*> threadPool;
SimpleMutex connectionPoolMutex;
DataStructures::Queue<AutopatcherRepositoryInterface *> connectionPool;
// How many users are currently patching
// unsigned int patchingUserCount;
SimpleMutex patchingUsersMutex;
DataStructures::List<SystemAddress> patchingUsers;
bool IncrementPatchingUserCount(SystemAddress sa);
bool DecrementPatchingUserCount(SystemAddress sa);
bool PatchingUserLimitReached(void) const;
virtual void OnFilePushesComplete( SystemAddress systemAddress, unsigned short setID );
virtual void OnSendAborted( SystemAddress systemAddress );
unsigned int maxConcurrentUsers;
// If maxConcurrentUsers is exceeded, then incoming requests are put into this queue
DataStructures::Queue<Packet *> userRequestWaitingQueue;
void AddToWaitingQueue(Packet *packet);
Packet *AbortOffWaitingQueue(void);
Packet *PopOffWaitingQueue(void);
AutopatcherServerLoadNotifier *loadNotifier;
void CallPacketCallback(Packet *packet, AutopatcherServerLoadNotifier::QueueOperation queueOperation);
void CallPatchCompleteCallback(const SystemAddress &systemAddress, AutopatcherServerLoadNotifier::PatchResult patchResult);
SLNet::RakString cache_appName;
FileList cache_patchedFiles;
FileList cache_addedFiles;
FileList cache_addedOrModifiedFileHashes;
FileList cache_deletedFiles;
double cache_minTime, cache_maxTime;
bool cacheLoaded;
bool allowDownloadOfOriginalUnmodifiedFiles;
};
} // namespace SLNet
#endif

View File

@ -0,0 +1,26 @@
#
# This file was taken from RakNet 4.082.
# Please see licenses/RakNet license.txt for the underlying license and related copyright.
#
#
# Modified work: Copyright (c) 2019, SLikeSoft UG (haftungsbeschr<68>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.
#
project(Autopatcher)
IF (UNIX AND NOT WIN32)
FILE(GLOB ALL_HEADER_SRCS *.h)
FILE(GLOB ALL_CPP_SRCS *.cpp)
include_directories(${SLIKENET_HEADER_FILES} ./ ${bzip2-1.0.3_SOURCE_DIR})
add_library(LibAutopatcher STATIC ${ALL_CPP_SRCS} ${ALL_HEADER_SRCS})
target_link_libraries (LibAutopatcher ${SLIKENET_COMMON_LIBS} LibBZip2)
ENDIF(UNIX AND NOT WIN32)
IF(USEMYSQL AND NOT DISABLEDEPENDENCIES)
add_subdirectory(AutopatcherMySQLRepository)
ENDIF(USEMYSQL AND NOT DISABLEDEPENDENCIES)
IF(USEPOSTGRESQL AND NOT DISABLEDEPENDENCIES)
add_subdirectory(AutopatcherPostgreRepository)
ENDIF(USEPOSTGRESQL AND NOT DISABLEDEPENDENCIES)

View File

@ -0,0 +1,872 @@
/*-
* Parts of this code are copyright 2003-2005 Colin Percival
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted providing that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This file was taken from RakNet 4.082.
* Please see licenses/RakNet license.txt for the underlying license and related copyright.
*
* Modified work: Copyright (c) 2016-2020, 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.
* Alternatively you are permitted to license the modifications under the Simplified BSD License.
*/
#define NOMINMAX
#include "MemoryCompressor.h"
#if 0
__FBSDID("$FreeBSD: src/usr.bin/bsdiff/bsdiff/bsdiff.c,v 1.1 2005/08/06 01:59:05 cperciva Exp $");
#endif
#include <sys/types.h>
#include "bzlib.h"
#ifndef _WIN32
#include <err.h>
#include <unistd.h>
#include "slikenet/linux_adapter.h"
#include "slikenet/osx_adapter.h"
#else
// KevinJ - Windows compatibility
typedef int ssize_t;
typedef unsigned char u_char;
typedef long off_t;
#include <wchar.h>
#include <io.h>
#define fseeko fseek
#define ftello ftell
static void err(int i, ...)
{
exit(i);
}
static void errx(int i, ...)
{
exit(i);
}
#endif
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits> // used for std::numeric_limits
#include <algorithm> // used for std::max
#ifndef MIN
#define MIN(x,y) (((x)<(y)) ? (x) : (y))
#endif
#ifndef _O_BINARY
#define _O_BINARY 0
#endif
#ifndef O_BINARY
#define O_BINARY _O_BINARY
#endif
static void split(off_t *I,off_t *V,off_t start,off_t len,off_t h)
{
off_t i,j,k,x,tmp,jj,kk;
if(len<16) {
for(k=start;k<start+len;k+=j) {
j=1;x=V[I[k]+h];
for(i=1;k+i<start+len;i++) {
if(V[I[k+i]+h]<x) {
x=V[I[k+i]+h];
j=0;
};
if(V[I[k+i]+h]==x) {
tmp=I[k+j];I[k+j]=I[k+i];I[k+i]=tmp;
j++;
};
};
for(i=0;i<j;i++) V[I[k+i]]=k+j-1;
if(j==1) I[k]=-1;
};
return;
};
x=V[I[start+len/2]+h];
jj=0;kk=0;
for(i=start;i<start+len;i++) {
if(V[I[i]+h]<x) jj++;
if(V[I[i]+h]==x) kk++;
};
jj+=start;kk+=jj;
i=start;j=0;k=0;
while(i<jj) {
if(V[I[i]+h]<x) {
i++;
} else if(V[I[i]+h]==x) {
tmp=I[i];I[i]=I[jj+j];I[jj+j]=tmp;
j++;
} else {
tmp=I[i];I[i]=I[kk+k];I[kk+k]=tmp;
k++;
};
};
while(jj+j<kk) {
if(V[I[jj+j]+h]==x) {
j++;
} else {
tmp=I[jj+j];I[jj+j]=I[kk+k];I[kk+k]=tmp;
k++;
};
};
if(jj>start) split(I,V,start,jj-start,h);
for(i=0;i<kk-jj;i++) V[I[jj+i]]=kk-1;
if(jj==kk-1) I[jj]=-1;
if(start+len>kk) split(I,V,kk,start+len-kk,h);
}
static void qsufsort(off_t *I,off_t *V,u_char *old,off_t oldsize)
{
off_t buckets[256];
off_t i,h,len;
//for(i=0;i<256;i++) buckets[i]=0;
memset(buckets, 0, sizeof(buckets));
for(i=0;i<oldsize;i++) buckets[old[i]]++;
for(i=1;i<256;i++) buckets[i]+=buckets[i-1];
for(i=255;i>0;i--) buckets[i]=buckets[i-1];
buckets[0]=0;
for(i=0;i<oldsize;i++) I[++buckets[old[i]]]=i;
I[0]=oldsize;
for(i=0;i<oldsize;i++) V[i]=buckets[old[i]];
V[oldsize]=0;
for(i=1;i<256;i++) if(buckets[i]==buckets[i-1]+1) I[buckets[i]]=-1;
I[0]=-1;
for(h=1;I[0]!=-(oldsize+1);h+=h) {
len=0;
for(i=0;i<oldsize+1;) {
if(I[i]<0) {
len-=I[i];
i-=I[i];
} else {
if(len) I[i-len]=-len;
len=V[I[i]]+1-i;
split(I,V,i,len,h);
i+=len;
len=0;
};
};
if(len) I[i-len]=-len;
};
for(i=0;i<oldsize+1;i++) I[V[i]]=i;
}
static off_t matchlen(u_char *old,off_t oldsize,u_char *_new,off_t newsize)
{
off_t i;
for(i=0;(i<oldsize)&&(i<newsize);i++)
if(old[i]!=_new[i]) break;
return i;
}
static off_t search(off_t *I,u_char *old,off_t oldsize,
u_char *_new,off_t newsize,off_t st,off_t en,off_t *pos)
{
off_t x,y;
if(en-st<2) {
x=matchlen(old+I[st],oldsize-I[st],_new,newsize);
y=matchlen(old+I[en],oldsize-I[en],_new,newsize);
if(x>y) {
*pos=I[st];
return x;
} else {
*pos=I[en];
return y;
}
};
x=st+(en-st)/2;
if(memcmp(old+I[x],_new,MIN(oldsize-I[x],newsize))<0) {
return search(I,old,oldsize,_new,newsize,x,en,pos);
} else {
return search(I,old,oldsize,_new,newsize,st,x,pos);
};
}
static void offtout(off_t x,u_char *buf)
{
off_t y;
if(x<0) y=-x; else y=x;
/*
buf[0]=y%256;y-=buf[0];
y=y/256;buf[1]=y%256;y-=buf[1];
y=y/256;buf[2]=y%256;y-=buf[2];
y=y/256;buf[3]=y%256;y-=buf[3];
y=y/256;buf[4]=y%256;y-=buf[4];
y=y/256;buf[5]=y%256;y-=buf[5];
y=y/256;buf[6]=y%256;y-=buf[6];
y=y/256;buf[7]=y%256;
*/
// Thanks to Oliver Smith for pointing out this optimization
buf[0] = (u_char)(y&(off_t)0x000000ff); y >>= 8 ;
buf[1] = (u_char)(y&(off_t)0x000000ff); y >>= 8 ;
buf[2] = (u_char)(y&(off_t)0x000000ff); y >>= 8 ;
buf[3] = (u_char)(y&(off_t)0x000000ff); y >>= 8 ;
buf[4] = (u_char)(y&(off_t)0x000000ff); y >>= 8 ;
buf[5] = (u_char)(y&(off_t)0x000000ff); y >>= 8 ;
buf[6] = (u_char)(y&(off_t)0x000000ff); y >>= 8 ;
buf[7] = (u_char)(y&(off_t)0x000000ff);// y >>= 8 ;
if(x<0) buf[7]|=0x80;
}
// This function modifies the main() function included in bsdiff.c of bsdiff-4.3 found at http://www.daemonology.net/bsdiff/
// It is changed to be a standalone function, to work entirely in memory, and to use my class MemoryCompressor as an interface to BZip
// Up to the caller to delete out
static bool CreatePatchInternal(const char *old, off_t oldsize, char *_new, off_t newsize, char **out, unsigned *outSize)
{
// int fd;
// u_char *old,*new;
// off_t oldsize,newsize;
off_t *I,*V;
off_t scan,len;
off_t pos = 0; // #low review whether this really is unnecessary - (presumably) unnecessary assignment - added to workaround false-positive of C4701
off_t lastscan,lastpos,lastoffset;
off_t oldscore,scsc;
off_t s,Sf,lenf,Sb,lenb;
off_t overlap,Ss,lens;
off_t i;
off_t dblen,eblen;
u_char *db,*eb;
u_char buf[8];
u_char header[32];
MemoryCompressor patch;
// unsigned outWriteOffset;
// FILE * pf;
// BZFILE * pfbz2;
// int bz2err;
// if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]);
/* Allocate oldsize+1 bytes instead of oldsize bytes to ensure
that we never try to malloc(0) and get a nullptr */
/*
if(((fd=_open(argv[1],O_RDONLY | _O_BINARY ,0))<0) ||
((oldsize=_lseek(fd,0,SEEK_END))==-1) ||
((old=malloc(oldsize+1))==nullptr) ||
(_lseek(fd,0,SEEK_SET)!=0) ||
(_read(fd,old,oldsize)!=oldsize) ||
(_close(fd)==-1)) err(1,"%s",argv[1]);
*/
if(((I=(off_t*)malloc((oldsize+1)*sizeof(off_t)))== nullptr) ||
((V=(off_t*)malloc((oldsize+1)*sizeof(off_t)))== nullptr))
// err(1,nullptr);
return false;
qsufsort(I,V,(u_char*)old,oldsize);
free(V);
/* Allocate newsize+1 bytes instead of newsize bytes to ensure
that we never try to malloc(0) and get a nullptr */
/*
if(((fd=_open(argv[2],O_RDONLY | _O_BINARY ,0))<0) ||
((newsize=_lseek(fd,0,SEEK_END))==-1) ||
((new=malloc(newsize+1))==nullptr) ||
(_lseek(fd,0,SEEK_SET)!=0) ||
(_read(fd,new,newsize)!=newsize) ||
(_close(fd)==-1)) err(1,"%s",argv[2]);
*/
if(((db=(u_char*)malloc(newsize+1))== nullptr) ||
((eb=(u_char*)malloc(newsize+1))== nullptr))
// err(1,nullptr);
{
free(I);
return false;
}
dblen=0;
eblen=0;
/* Create the patch file */
// if (fopen_s(&pf, argv[3], "wb") !=0)
// err(1, "%s", argv[3]);
/* Header is
0 8 "BSDIFF40"
8 8 length of bzip2ed ctrl block
16 8 length of bzip2ed diff block
24 8 length of new file */
/* File is
0 32 Header
32 ?? Bzip2ed ctrl block
?? ?? Bzip2ed diff block
?? ?? Bzip2ed extra block */
memcpy(header,"BSDIFF40",8);
offtout(0, header + 8);
offtout(0, header + 16);
offtout(newsize, header + 24);
// if (fwrite(header, 32, 1, pf) != 1)
// err(1, "fwrite(%s)", argv[3]);
// Allocate enough to hold any output
// *out = (char*) malloc(oldsize+newsize);
// Copy out the header
// memcpy(*out, header, 32);
//outWriteOffset=32;
/* Compute the differences, writing ctrl as we go */
// if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == nullptr)
// errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err);
scan=0;len=0;
lastscan=0;lastpos=0;lastoffset=0;
while(scan<newsize) {
oldscore=0;
for(scsc=scan+=len;scan<newsize;scan++) {
len=search(I,(u_char*)old,oldsize,(u_char*)_new+scan,newsize-scan,
0,oldsize,&pos);
for(;scsc<scan+len;scsc++)
if((scsc+lastoffset<oldsize) &&
(old[scsc+lastoffset] == _new[scsc]))
oldscore++;
if(((len==oldscore) && (len!=0)) ||
(len>oldscore+8)) break;
if((scan+lastoffset<oldsize) &&
(old[scan+lastoffset] == _new[scan]))
oldscore--;
};
if((len!=oldscore) || (scan==newsize)) {
s=0;Sf=0;lenf=0;
for(i=0;(lastscan+i<scan)&&(lastpos+i<oldsize);) {
if(old[lastpos+i]==_new[lastscan+i]) s++;
i++;
if(s*2-i>Sf*2-lenf) { Sf=s; lenf=i; };
};
lenb=0;
if(scan<newsize) {
s=0;Sb=0;
for(i=1;(scan>=lastscan+i)&&(pos>=i);i++) {
if(old[pos-i]==_new[scan-i]) s++;
if(s*2-i>Sb*2-lenb) { Sb=s; lenb=i; };
};
};
if(lastscan+lenf>scan-lenb) {
overlap=(lastscan+lenf)-(scan-lenb);
s=0;Ss=0;lens=0;
for(i=0;i<overlap;i++) {
if(_new[lastscan+lenf-overlap+i]==
old[lastpos+lenf-overlap+i]) s++;
if(_new[scan-lenb+i]==
old[pos-lenb+i]) s--;
if(s>Ss) { Ss=s; lens=i+1; };
};
lenf+=lens-overlap;
lenb-=lens;
};
for(i=0;i<lenf;i++)
db[dblen+i]=_new[lastscan+i]-old[lastpos+i];
for(i=0;i<(scan-lenb)-(lastscan+lenf);i++)
eb[eblen+i]=_new[lastscan+lenf+i];
dblen+=lenf;
eblen+=(scan-lenb)-(lastscan+lenf);
offtout(lenf,buf);
if (patch.Compress((char*)buf, 8, false)==false)
{
free(db);
free(eb);
free(I);
return false;
}
//BZ2_bzWrite(&bz2err, pfbz2, buf, 8);
//if (bz2err != BZ_OK)
// errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
offtout((scan-lenb)-(lastscan+lenf),buf);
if (patch.Compress((char*)buf, 8, false)==false)
{
free(db);
free(eb);
free(I);
return false;
}
//BZ2_bzWrite(&bz2err, pfbz2, buf, 8);
//if (bz2err != BZ_OK)
// errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
offtout((pos-lenb)-(lastpos+lenf),buf);
if (patch.Compress((char*)buf, 8, false)==false)
{
free(db);
free(eb);
free(I);
return false;
}
//BZ2_bzWrite(&bz2err, pfbz2, buf, 8);
//if (bz2err != BZ_OK)
// errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
lastscan=scan-lenb;
lastpos=pos-lenb;
lastoffset=pos-scan;
};
};
// BZ2_bzWriteClose(&bz2err, pfbz2, 0, nullptr, nullptr);
// if (bz2err != BZ_OK)
// errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err);
/* Compute size of compressed ctrl data */
// if ((len = ftello(pf)) == -1)
// err(1, "ftello");
if (patch.Compress(0,0,true)==false)
{
free(db);
free(eb);
free(I);
return false;
}
len=patch.GetTotalOutputSize()+32; // test: len should be 188
offtout(len-32, header + 8);
//memcpy(*out+outWriteOffset, patch.GetOutput(), patch.GetTotalOutputSize());
//outWriteOffset+=patch.GetTotalOutputSize();
//patch.Clear(_FILE_AND_LINE_);
/* Write compressed diff data */
// if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == nullptr)
// errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err);
// BZ2_bzWrite(&bz2err, pfbz2, db, dblen);
if (patch.Compress((char*)db,dblen,true)==false)
{
free(db);
free(eb);
free(I);
return false;
}
// memcpy(*out+outWriteOffset, patch.GetOutput(), patch.GetTotalOutputSize());
// outWriteOffset+=patch.GetTotalOutputSize();
// patch.Clear(_FILE_AND_LINE_);
// if (bz2err != BZ_OK)
// errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
// BZ2_bzWriteClose(&bz2err, pfbz2, 0, nullptr, nullptr);
// if (bz2err != BZ_OK)
// errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err);
/* Compute size of compressed diff data */
// if ((newsize = ftello(pf)) == -1)
// err(1, "ftello");
newsize=32+patch.GetTotalOutputSize();
offtout(newsize - len, header + 16);
// memcpy(*out+outWriteOffset, patch.GetOutput(), patch.GetTotalOutputSize());
// outWriteOffset+=patch.GetTotalOutputSize();
// patch.Clear(_FILE_AND_LINE_);
/* Write compressed extra data */
// if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == nullptr)
// errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err);
// BZ2_bzWrite(&bz2err, pfbz2, eb, eblen);
if (patch.Compress((char*)eb,eblen,true)==false)
{
free(db);
free(eb);
free(I);
return false;
}
// memcpy(*out+outWriteOffset, patch.GetOutput(), patch.GetTotalOutputSize());
// outWriteOffset+=patch.GetTotalOutputSize();
// patch.Clear(_FILE_AND_LINE_);
// if (bz2err != BZ_OK)
// errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
// BZ2_bzWriteClose(&bz2err, pfbz2, 0, nullptr, nullptr);
// if (bz2err != BZ_OK)
// errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err);
/* Seek to the beginning, write the header, and close the file */
// if (fseeko(pf, 0, SEEK_SET))
// err(1, "fseeko");
// if (fwrite(header, 32, 1, pf) != 1)
// err(1, "fwrite(%s)", argv[3]);
// if (fclose(pf))
// err(1, "fclose");
// memcpy(*out,header,32);
// *out=(char*) realloc(*out, outWriteOffset);
// *outSize=outWriteOffset;
*outSize=patch.GetTotalOutputSize()+32;
*out = new char [*outSize];
memcpy(*out, header, 32);
memcpy(*out+32, patch.GetOutput(), patch.GetTotalOutputSize());
/* Free the memory we used */
free(db);
free(eb);
free(I);
//free(old);
//free(new);
return true;
}
// #med - deprecate/remove this overload (alongside the other overloads except for the off_t version)
// Note: overloads provided, so to ensure we are API-wise backwards compatible with RakNet 4.082
// (i.e. for callers passing int rather than off_t types which due to the added unsigned overload would
// now trigger compile errors due to the ambiguous parameters)
bool CreatePatch(const char *old, unsigned oldsize, char *_new, unsigned int newsize, char **out, unsigned *outSize)
{
// we must ensure that we don't pass >= off_t-max values to CreatePatch since otherwise the behavior is undefined due to
// internal integer overflows when processing the pointers (in certain cases)
// hence test the values first and simply error out if we get too large values (effectively limiting this function to work
// with file sizes of 2 GB on Windows and 32-bit Linux/OSx (Linux/OSx 64-bit: will remain working with 4GB sized files as RakNet did
// due to off_t being 64-bit)
if (std::max(oldsize, newsize) > static_cast<unsigned long>(std::numeric_limits<off_t>::max())) {
return false;
}
return CreatePatchInternal(old, static_cast<off_t>(oldsize), _new, static_cast<off_t>(newsize), out, outSize);
}
bool CreatePatch(const char *old, int oldsize, char *_new, int newsize, char **out, unsigned *outSize)
{
return CreatePatchInternal(old, static_cast<off_t>(oldsize), _new, static_cast<off_t>(newsize), out, outSize);
}
bool CreatePatch(const char *old, int oldsize, char *_new, unsigned int newsize, char **out, unsigned *outSize)
{
if (newsize > static_cast<unsigned long>(std::numeric_limits<off_t>::max())) {
return false;
}
return CreatePatchInternal(old, static_cast<off_t>(oldsize), _new, static_cast<off_t>(newsize), out, outSize);
}
bool CreatePatch(const char *old, unsigned oldsize, char *_new, int newsize, char **out, unsigned *outSize)
{
if (oldsize > static_cast<unsigned long>(std::numeric_limits<off_t>::max())) {
return false;
}
return CreatePatchInternal(old, static_cast<off_t>(oldsize), _new, static_cast<off_t>(newsize), out, outSize);
}
bool CreatePatch(const char *old, off_t oldsize, char *_new, off_t newsize, char **out, unsigned *outSize)
{
return CreatePatchInternal(old, oldsize, _new, newsize, out, outSize);
}
int TestDiffInMemory(int argc,char *argv[])
{
char *old = nullptr; // unnecessary assignment - added to workaround false-positive of C4701
off_t oldsize = 0; // unnecessary assignment - added to workaround false-positive of C4701
char *_new = nullptr; // unnecessary assignment - added to workaround false-positive of C4701
off_t newsize = 0; // unnecessary assignment - added to workaround false-positive of C4701
char *out;
unsigned outSize;
if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]);
int fd;
FILE * pf;
if(((fd=_open(argv[1],O_RDONLY | _O_BINARY ,0))<0) ||
((oldsize=_lseek(fd,0,SEEK_END))==-1) ||
((old=(char*)malloc(oldsize+1))== nullptr) ||
(_lseek(fd,0,SEEK_SET)!=0) ||
(_read(fd,old,oldsize)!=oldsize) ||
(_close(fd)==-1)) err(1,"%s",argv[1]);
if(((fd=_open(argv[2],O_RDONLY | _O_BINARY ,0))<0) ||
((newsize=_lseek(fd,0,SEEK_END))==-1) ||
((_new=(char*)malloc(newsize+1))== nullptr) ||
(_lseek(fd,0,SEEK_SET)!=0) ||
(_read(fd,_new,newsize)!=newsize) ||
(_close(fd)==-1)) err(1,"%s",argv[2]);
int res = CreatePatch(old, oldsize, _new, newsize, &out, &outSize);
if (fopen_s(&pf, argv[3], "wb") != 0)
err(1, "%s", argv[3]);
fwrite(out,outSize,1,pf);
fclose(pf);
delete[] out;
return res;
}
int DIFF_main(int argc,char *argv[])
{
int fd;
u_char *old = nullptr; // unnecessary assignment - added to workaround false-positive of C4701
u_char *_new;
off_t oldsize = 0; // unnecessary assignment - added to workaround false-positive of C4701
off_t newsize = 0; // unnecessary assignment - added to workaround false-positive of C4701
off_t *I;
off_t *V = nullptr; // unnecessary assignment - added to workaround false-positive of C4701
off_t scan,len;
off_t pos = 0; // #low review whether this really is unnecessary - (presumably) unnecessary assignment - added to workaround false-positive of C4701
off_t lastscan,lastpos,lastoffset;
off_t oldscore,scsc;
off_t s,Sf,lenf,Sb,lenb;
off_t overlap,Ss,lens;
off_t i;
off_t dblen,eblen;
u_char *db,*eb;
u_char buf[8];
u_char header[32];
FILE * pf;
BZFILE * pfbz2;
int bz2err;
int bytesWritten=0;
if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]);
/* Allocate oldsize+1 bytes instead of oldsize bytes to ensure
that we never try to malloc(0) and get a nullptr */
if(((fd=_open(argv[1],O_RDONLY|O_BINARY,0))<0) ||
((oldsize=_lseek(fd,0,SEEK_END))==-1) ||
((old=(u_char*)malloc(oldsize+1))== nullptr) ||
(_lseek(fd,0,SEEK_SET)!=0) ||
(_read(fd,old,oldsize)!=oldsize) ||
(_close(fd)==-1)) err(1,"%s",argv[1]);
if(((I=(off_t*)malloc((oldsize+1)*sizeof(off_t)))== nullptr) ||
((V=(off_t*)malloc((oldsize+1)*sizeof(off_t)))== nullptr)) err(1, nullptr);
qsufsort(I,V,old,oldsize);
free(V);
/* Allocate newsize+1 bytes instead of newsize bytes to ensure
that we never try to malloc(0) and get a nullptr */
if(((fd=_open(argv[2],O_RDONLY|O_BINARY,0))<0) ||
((newsize=_lseek(fd,0,SEEK_END))==-1) ||
((_new=(u_char*)malloc(newsize+1))== nullptr) ||
(_lseek(fd,0,SEEK_SET)!=0) ||
(_read(fd,_new,newsize)!=newsize) ||
(_close(fd)==-1)) err(1,"%s",argv[2]);
if(((db=(u_char*)malloc(newsize+1))== nullptr) ||
((eb=(u_char*)malloc(newsize+1))== nullptr)) err(1, nullptr);
dblen=0;
eblen=0;
/* Create the patch file */
if (fopen_s(&pf, argv[3], "wb") != 0)
err(1, "%s", argv[3]);
/* Header is
0 8 "BSDIFF40"
8 8 length of bzip2ed ctrl block
16 8 length of bzip2ed diff block
24 8 length of new file */
/* File is
0 32 Header
32 ?? Bzip2ed ctrl block
?? ?? Bzip2ed diff block
?? ?? Bzip2ed extra block */
memcpy(header,"BSDIFF40",8);
offtout(0, header + 8);
offtout(0, header + 16);
offtout(newsize, header + 24);
if (fwrite(header, 32, 1, pf) != 1)
err(1, "fwrite(%s)", argv[3]);
/* Compute the differences, writing ctrl as we go */
if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == nullptr)
errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err);
scan=0;len=0;
lastscan=0;lastpos=0;lastoffset=0;
while(scan<newsize) {
oldscore=0;
for(scsc=scan+=len;scan<newsize;scan++) {
len=search(I,old,oldsize,_new+scan,newsize-scan,
0,oldsize,&pos);
for(;scsc<scan+len;scsc++)
if((scsc+lastoffset<oldsize) &&
(old[scsc+lastoffset] == _new[scsc]))
oldscore++;
if(((len==oldscore) && (len!=0)) ||
(len>oldscore+8)) break;
if((scan+lastoffset<oldsize) &&
(old[scan+lastoffset] == _new[scan]))
oldscore--;
};
if((len!=oldscore) || (scan==newsize)) {
s=0;Sf=0;lenf=0;
for(i=0;(lastscan+i<scan)&&(lastpos+i<oldsize);) {
if(old[lastpos+i]==_new[lastscan+i]) s++;
i++;
if(s*2-i>Sf*2-lenf) { Sf=s; lenf=i; };
};
lenb=0;
if(scan<newsize) {
s=0;Sb=0;
for(i=1;(scan>=lastscan+i)&&(pos>=i);i++) {
if(old[pos-i]==_new[scan-i]) s++;
if(s*2-i>Sb*2-lenb) { Sb=s; lenb=i; };
};
};
if(lastscan+lenf>scan-lenb) {
overlap=(lastscan+lenf)-(scan-lenb);
s=0;Ss=0;lens=0;
for(i=0;i<overlap;i++) {
if(_new[lastscan+lenf-overlap+i]==
old[lastpos+lenf-overlap+i]) s++;
if(_new[scan-lenb+i]==
old[pos-lenb+i]) s--;
if(s>Ss) { Ss=s; lens=i+1; };
};
lenf+=lens-overlap;
lenb-=lens;
};
for(i=0;i<lenf;i++)
db[dblen+i]=_new[lastscan+i]-old[lastpos+i];
for(i=0;i<(scan-lenb)-(lastscan+lenf);i++)
eb[eblen+i]=_new[lastscan+lenf+i];
dblen+=lenf;
eblen+=(scan-lenb)-(lastscan+lenf);
offtout(lenf,buf);
BZ2_bzWrite(&bz2err, pfbz2, buf, 8);
if (bz2err != BZ_OK)
errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
bytesWritten+=8;
// printf("bz2err 8 %i\n", bytesWritten);
offtout((scan-lenb)-(lastscan+lenf),buf);
BZ2_bzWrite(&bz2err, pfbz2, buf, 8);
if (bz2err != BZ_OK)
errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
bytesWritten+=8;
// printf("bz2err 8 %i\n", bytesWritten);
offtout((pos-lenb)-(lastpos+lenf),buf);
BZ2_bzWrite(&bz2err, pfbz2, buf, 8);
if (bz2err != BZ_OK)
errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
bytesWritten+=8;
// printf("bz2err 8 %i\n", bytesWritten);
lastscan=scan-lenb;
lastpos=pos-lenb;
lastoffset=pos-scan;
};
};
BZ2_bzWriteClose(&bz2err, pfbz2, 0, nullptr, nullptr);
if (bz2err != BZ_OK)
errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err);
/* Compute size of compressed ctrl data */
if ((len = ftello(pf)) == -1)
err(1, "ftello");
offtout(len-32, header + 8);
/* Write compressed diff data */
if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == nullptr)
errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err);
BZ2_bzWrite(&bz2err, pfbz2, db, dblen);
bytesWritten+=dblen;
// printf("bz2err dblen %i %i\n", dblen, bytesWritten);
if (bz2err != BZ_OK)
errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
BZ2_bzWriteClose(&bz2err, pfbz2, 0, nullptr, nullptr);
if (bz2err != BZ_OK)
errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err);
/* Compute size of compressed diff data */
if ((newsize = ftello(pf)) == -1)
err(1, "ftello");
offtout(newsize - len, header + 16);
/* Write compressed extra data */
if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == nullptr)
errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err);
BZ2_bzWrite(&bz2err, pfbz2, eb, eblen);
if (bz2err != BZ_OK)
errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
bytesWritten+=eblen;
//printf("bz2err eblen %i %i\n", eblen, bytesWritten);
BZ2_bzWriteClose(&bz2err, pfbz2, 0, nullptr, nullptr);
if (bz2err != BZ_OK)
errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err);
// REMOVEME
// if ((newsize = ftello(pf)) == -1)
// err(1, "ftello");
/* Seek to the beginning, write the header, and close the file */
if (fseeko(pf, 0, SEEK_SET))
err(1, "fseeko");
if (fwrite(header, 32, 1, pf) != 1)
err(1, "fwrite(%s)", argv[3]);
if (fclose(pf))
err(1, "fclose");
/* Free the memory we used */
free(db);
free(eb);
free(I);
free(old);
free(_new);
return 0;
}

View File

@ -0,0 +1,20 @@
/*
* This file was taken from RakNet 4.082.
* Please see licenses/RakNet license.txt for the underlying license and related copyright.
*
* 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.
*/
#ifdef _WIN32
typedef long off_t;
#endif
/// Given \a old and \a new , return \a out which will contain a patch to get from \a old to \a new . \a out is allocated for you.
bool CreatePatch(const char *old, off_t oldsize, char *_new, off_t newsize, char **out, unsigned *outSize);
bool CreatePatch(const char *old, int oldsize, char *_new, int newsize, char **out, unsigned *outSize);
bool CreatePatch(const char *old, unsigned oldsize, char *_new, unsigned int newsize, char **out, unsigned *outSize);
bool CreatePatch(const char *old, int oldsize, char *_new, unsigned int newsize, char **out, unsigned *outSize);
bool CreatePatch(const char *old, unsigned oldsize, char *_new, int newsize, char **out, unsigned *outSize);

View File

@ -0,0 +1,242 @@
/*
* 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.
*
*/
// See http://www.bzip.org/1.0.3/bzip2-manual-1.0.3.html#low-level for docs on bzip
#include "MemoryCompressor.h"
#include <assert.h>
#include <stdlib.h>
CompressorBase::CompressorBase()
{
output=0;
allocatedSize=0;
streamInited=false;
stream.bzalloc=0;
stream.bzfree=0;
stream.opaque=0;
totalRead=totalWritten=0;
}
CompressorBase::~CompressorBase()
{
}
MemoryCompressor::MemoryCompressor()
{
compressedInputLength=0;
}
MemoryCompressor::~MemoryCompressor()
{
Clear();
}
MemoryDecompressor::~MemoryDecompressor()
{
Clear();
}
bool MemoryCompressor::Compress(char *input, const unsigned inputLength, bool finish)
{
int res;
unsigned inBefore,outBefore, read;
unsigned written;
if (output==0)
{
allocatedSize=inputLength;
if (allocatedSize < BZ_MAX_UNUSED)
allocatedSize=BZ_MAX_UNUSED;
output=(char*)malloc(allocatedSize);
}
if (streamInited==false)
{
res = BZ2_bzCompressInit ( &stream,
9, // x 100K Block size. Memory to use = 400k + ( 8 x block size ). So this is 400K + 9 x 900K = 7.6 megabytes. Larger sizes give better compression.
0, // Verbosity.
0 ); // Default work factor
streamInited=true;
if (res!=BZ_OK)
return false;
}
read=written=0;
unsigned readThisSession;
readThisSession=0;
compressedInputLength+=inputLength;
if (totalWritten==allocatedSize)
{
allocatedSize+=inputLength;
output=(char*)realloc(output, allocatedSize);
}
while(1)
{
stream.next_out=output+totalWritten;
stream.avail_in=inputLength-readThisSession;
stream.avail_out=allocatedSize-totalWritten;
stream.next_in=input+readThisSession;
inBefore=stream.total_in_lo32;
outBefore=stream.total_out_lo32;
//printf("%i\n", stream.avail_in);
res = BZ2_bzCompress( &stream, finish ? BZ_FINISH : BZ_RUN );
read=stream.total_in_lo32-inBefore;
written=stream.total_out_lo32-outBefore;
totalRead+=read;
totalWritten+=written;
readThisSession+=read;
if (stream.avail_out>0)
// if ((stream.avail_in==0 && stream.avail_out>0) || (read==0 && written==0))
{
if (finish)
{
allocatedSize=GetTotalOutputSize();
output=(char*)realloc(output,allocatedSize);
BZ2_bzCompressEnd( &stream );
streamInited=false;
}
return true;
}
if (totalWritten==allocatedSize || read==0)
{
allocatedSize*=2;
output=(char*)realloc(output, allocatedSize);
}
}
}
bool MemoryDecompressor::Decompress(char *input, const unsigned inputLength, bool ignoreStreamEnd)
{
unsigned inBefore,outBefore, read;
unsigned written;
int res;
if (output==0)
{
allocatedSize=inputLength*2;
if (allocatedSize < BZ_MAX_UNUSED)
allocatedSize=BZ_MAX_UNUSED;
output=(char*)malloc(allocatedSize);
}
if (streamInited==false)
{
res = BZ2_bzDecompressInit( &stream,
0, // Verbosity.
0 ); // Disable small memory usage
streamInited=true;
if (res!=BZ_OK)
return false;
}
unsigned readThisSession;
readThisSession=0;
read=written=0;
if (totalWritten==allocatedSize)
{
allocatedSize+=inputLength*4;
output=(char*)realloc(output, allocatedSize);
}
while(1)
{
stream.next_out=output+totalWritten;
stream.avail_in=inputLength-readThisSession;
stream.avail_out=allocatedSize-totalWritten;
stream.next_in=input+readThisSession;
inBefore=stream.total_in_lo32;
outBefore=stream.total_out_lo32;
res = BZ2_bzDecompress( &stream );
read=stream.total_in_lo32-inBefore;
written=stream.total_out_lo32-outBefore;
readThisSession+=read;
totalRead+=read;
totalWritten+=written;
if (res==BZ_STREAM_END)
{
BZ2_bzDecompressEnd( &stream );
if (ignoreStreamEnd==true)
{
// Stream end marker but there is more data so just keep reading
res = BZ2_bzDecompressInit( &stream,
0, // Verbosity.
0 ); // Disable small memory usage
}
else
{
streamInited=false;
return true;
}
}
else if ((stream.avail_in==0 && stream.avail_out>0) || (read==0 && written==0))
{
allocatedSize=GetTotalOutputSize();
output=(char*)realloc(output,allocatedSize);
return true;
}
else if (res!=BZ_OK)
{
Clear();
return false;
}
if (totalWritten==allocatedSize || read==0)
{
allocatedSize+=inputLength*4;
output=(char*)realloc(output, allocatedSize);
}
}
}
char *CompressorBase::GetOutput(void) const
{
return output;
}
unsigned MemoryCompressor::GetCompressedInputLength(void) const
{
return compressedInputLength;
}
unsigned CompressorBase::GetTotalOutputSize(void) const
{
return totalWritten;
}
unsigned CompressorBase::GetTotalInputSize(void) const
{
return totalRead;
}
void MemoryCompressor::Clear(void)
{
if (output)
{
free(output);
output=0;
}
if (streamInited)
BZ2_bzCompressEnd( &stream );
totalRead=totalWritten=compressedInputLength=0;
}
void MemoryDecompressor::Clear(void)
{
if (output)
{
free(output);
output=0;
}
if (streamInited)
BZ2_bzDecompressEnd( &stream );
}

View File

@ -0,0 +1,83 @@
/*
* 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.
*
*/
#ifndef _MEMORY_COMPRESSOR_H
#define _MEMORY_COMPRESSOR_H
#include "bzlib.h"
/// \internal
/// Do not use this class directly. Use MemoryCompressor and MemoryDecompressor.
class CompressorBase
{
public:
CompressorBase();
~CompressorBase();
/// Get the compressed data. The length currently written is returned from GetTotalOutputSize().
/// \return The compressed data
char *GetOutput(void) const;
/// \return The number of bytes outputted so far.
unsigned GetTotalOutputSize(void) const;
/// \return The number of bytes input by the user so far
unsigned GetTotalInputSize(void) const;
protected:
bz_stream stream;
char *output;
unsigned allocatedSize;
unsigned totalRead, totalWritten;
bool streamInited;
};
/// Compress one or more blocks of data
class MemoryCompressor : public CompressorBase
{
public:
MemoryCompressor();
~MemoryCompressor();
/// Compress a block of data. Pass true to finish if this is the last block in the series. If you don't know if it's the last block, you can call it again with 0 for inputLength
/// \note Data passed to input isn't necessarily immediately compressed to output. You can force a write by passing true to finish.
/// Multiple calls concatenate the written data.
/// \param[in] input A pointer to a block of data
/// \param[in] inputLength The length of input
/// \param[in] finish Write the last of the data.
bool Compress(char *input, const unsigned inputLength, bool finish);
/// Resets the compressor and all data.
void Clear(void);
// Number of bytes total passed to /a inputLength in the Compress() function
unsigned GetCompressedInputLength(void) const;
protected:
unsigned compressedInputLength;
};
class MemoryDecompressor : public CompressorBase
{
public:
~MemoryDecompressor();
/// Read \a inputLength bytes of compressed data from \a input
/// Writes the decompressed output to GetOutput(). Note that unlike the class MemoryCompressor, output data is updated immediately and not internally buffered
/// \param[in] input A pointer to a block of data
/// \param[in] inputLength The length of input
/// \param[in] ignoreStreamEnd Normally when Compress is called with finish==true stream end markers are placed. These are honored, such that the read will end early if a stream marker is hit. Pass true to ignore this and just output all the data.
bool Decompress(char *input, const unsigned inputLength, bool ignoreStreamEnd);
/// Resets the compressor and all data.
void Clear(void);
};
#endif