|
|
|
@ -39,455 +39,245 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
|
|
|
|
Mead, WA 99021 |
|
|
|
|
|
|
|
|
|
*==LICENSE==*/ |
|
|
|
|
#include "plResPatcher.h" |
|
|
|
|
|
|
|
|
|
#include "hsResMgr.h" |
|
|
|
|
#include "plResPatcher.h" |
|
|
|
|
|
|
|
|
|
#include "plAgeDescription/plAgeManifest.h" |
|
|
|
|
#include "plResMgr/plResManager.h" |
|
|
|
|
#include "plFile/plFileUtils.h" |
|
|
|
|
#include "plFile/plEncryptedStream.h" |
|
|
|
|
#include "plAgeLoader/plAgeLoader.h" |
|
|
|
|
#include "plCompression/plZlibStream.h" |
|
|
|
|
#include "plAudioCore/plAudioFileReader.h" |
|
|
|
|
#include "plProgressMgr/plProgressMgr.h" |
|
|
|
|
|
|
|
|
|
#include "pnAsyncCore/pnAsyncCore.h" |
|
|
|
|
#include "pnNetCli/pnNetCli.h" |
|
|
|
|
#include "plEncryption/plChecksum.h" |
|
|
|
|
#include "plFile/plFileUtils.h" |
|
|
|
|
#include "plMessage/plResPatcherMsg.h" |
|
|
|
|
#include "pnNetBase/pnNbError.h" |
|
|
|
|
#include "plNetGameLib/plNetGameLib.h" |
|
|
|
|
|
|
|
|
|
#include "pnDispatch/plDispatch.h" |
|
|
|
|
#include "plProgressMgr/plProgressMgr.h" |
|
|
|
|
#include "plStatusLog/plStatusLog.h" |
|
|
|
|
|
|
|
|
|
static const unsigned kMaxDownloadTries = 10; |
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
class plDownloadStream : public plZlibStream |
|
|
|
|
class plResDownloadStream : public plZlibStream |
|
|
|
|
{ |
|
|
|
|
private: |
|
|
|
|
plOperationProgress* fProgress; |
|
|
|
|
unsigned fBytesReceived; |
|
|
|
|
public: |
|
|
|
|
plDownloadStream(plOperationProgress* progress) : fProgress(progress), fBytesReceived(0), plZlibStream() {} |
|
|
|
|
virtual ~plDownloadStream() {} |
|
|
|
|
bool fIsZipped; |
|
|
|
|
|
|
|
|
|
virtual UInt32 Write(UInt32 byteCount, const void* buffer); |
|
|
|
|
public: |
|
|
|
|
plResDownloadStream(plOperationProgress* prog, const wchar_t* reqFile) |
|
|
|
|
: fProgress(prog)
|
|
|
|
|
{
|
|
|
|
|
fIsZipped = wcscmp(plFileUtils::GetFileExt(reqFile), L"gz") == 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void RewindProgress() {fProgress->Increment(-(hsScalar)fBytesReceived);} // rewind the progress bar by as far as we got
|
|
|
|
|
UInt32 Write(UInt32 count, const void* buf) |
|
|
|
|
{ |
|
|
|
|
fProgress->Increment((hsScalar)count); |
|
|
|
|
if (fIsZipped) |
|
|
|
|
return plZlibStream::Write(count, buf); |
|
|
|
|
else |
|
|
|
|
return fOutput->Write(count, buf); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
UInt32 plDownloadStream::Write(UInt32 byteCount, const void* buffer) |
|
|
|
|
{ |
|
|
|
|
fProgress->Increment((hsScalar)byteCount); |
|
|
|
|
fBytesReceived += byteCount; |
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
return plZlibStream::Write(byteCount, buffer); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
static void DownloadFileCallback(ENetError result, void* param, const wchar filename[], hsStream* writer) |
|
|
|
|
static void FileDownloaded( |
|
|
|
|
ENetError result, |
|
|
|
|
void* param, |
|
|
|
|
const wchar_t filename[], |
|
|
|
|
hsStream* writer)
|
|
|
|
|
{ |
|
|
|
|
plResPatcher* patcher = (plResPatcher*)param; |
|
|
|
|
char* name = hsWStringToString(filename); |
|
|
|
|
writer->Close(); |
|
|
|
|
delete writer; |
|
|
|
|
|
|
|
|
|
// Retry download unless shutting down or file not found
|
|
|
|
|
switch (result) { |
|
|
|
|
switch (result) |
|
|
|
|
{ |
|
|
|
|
case kNetSuccess: |
|
|
|
|
writer->Close(); |
|
|
|
|
patcher->DoneWithFile(true); |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
PatcherLog(kStatus, " Download Complete: %s", name); |
|
|
|
|
patcher->IssueRequest(); |
|
|
|
|
delete[] name; |
|
|
|
|
return; |
|
|
|
|
case kNetErrFileNotFound: |
|
|
|
|
case kNetErrRemoteShutdown: |
|
|
|
|
writer->Close(); |
|
|
|
|
patcher->DoneWithFile(false); |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
PatcherLog(kError, " Download Failed: %s not found", name); |
|
|
|
|
break; |
|
|
|
|
default: |
|
|
|
|
writer->Rewind(); |
|
|
|
|
NetCliFileDownloadRequest( |
|
|
|
|
filename, |
|
|
|
|
writer, |
|
|
|
|
DownloadFileCallback, |
|
|
|
|
param |
|
|
|
|
); |
|
|
|
|
break; |
|
|
|
|
char* error = hsWStringToString(NetErrorToString(result)); |
|
|
|
|
PatcherLog(kError, " Download Failed: %s", error); |
|
|
|
|
delete[] error; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Failure case
|
|
|
|
|
patcher->Finish(false); |
|
|
|
|
delete[] name; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void ManifestCallback(ENetError result, void* param, const wchar group[], const NetCliFileManifestEntry manifest[], unsigned entryCount) |
|
|
|
|
static void ManifestDownloaded( |
|
|
|
|
ENetError result,
|
|
|
|
|
void* param,
|
|
|
|
|
const wchar_t group[],
|
|
|
|
|
const NetCliFileManifestEntry manifest[],
|
|
|
|
|
UInt32 entryCount) |
|
|
|
|
{ |
|
|
|
|
plResPatcher* patcher = (plResPatcher*)param; |
|
|
|
|
patcher->DoneWithManifest(result == kNetSuccess, manifest, entryCount); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//// Constructor/Destructor //////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
plResPatcher::plResPatcher(const char* ageToPatch, bool showAgeName) |
|
|
|
|
{ |
|
|
|
|
fAgeToPatch = ageToPatch; |
|
|
|
|
fAlwaysShowAgeName = showAgeName; |
|
|
|
|
IInit(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void plResPatcher::IInit() |
|
|
|
|
{ |
|
|
|
|
PatcherLog(kHeader, "--- Starting patch process for %s ---", fAgeToPatch.c_str()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
plResPatcher::~plResPatcher() |
|
|
|
|
{ |
|
|
|
|
PatcherLog(kHeader, "--- Patch process done for %s ---", fAgeToPatch.c_str()); |
|
|
|
|
|
|
|
|
|
for (MfsFileVec::iterator i = fMfsVec.begin(); i != fMfsVec.end(); ++i) |
|
|
|
|
{ |
|
|
|
|
plManifestFile* file = (*i); |
|
|
|
|
delete file; |
|
|
|
|
char* name = hsWStringToString(group); |
|
|
|
|
if (IS_NET_SUCCESS(result)) |
|
|
|
|
PatcherLog(kInfo, " Downloaded manifest %s", name); |
|
|
|
|
else { |
|
|
|
|
PatcherLog(kError, " Failed to download manifest %s", name); |
|
|
|
|
patcher->Finish(false); |
|
|
|
|
delete[] name; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
fMfsVec.clear(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
UInt32 plResPatcher::IGetDownloadSize() |
|
|
|
|
{ |
|
|
|
|
if (!IGetAgeManifest()) |
|
|
|
|
return 0; |
|
|
|
|
|
|
|
|
|
#ifdef PLASMA_EXTERNAL_RELEASE |
|
|
|
|
bool showAgeName = fAlwaysShowAgeName; |
|
|
|
|
#else |
|
|
|
|
bool showAgeName = true; |
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
char msg[128]; |
|
|
|
|
if (!fAgeToPatch.empty()) |
|
|
|
|
for (UInt32 i = 0; i < entryCount; ++i) |
|
|
|
|
{ |
|
|
|
|
if (showAgeName) |
|
|
|
|
sprintf(msg, "Checking age %s...", fAgeToPatch.c_str()); |
|
|
|
|
else |
|
|
|
|
strcpy(msg, "Checking age..."); |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
sprintf(msg, "Checking..."); |
|
|
|
|
|
|
|
|
|
plOperationProgress* progress = plProgressMgr::GetInstance()->RegisterOperation((hsScalar)(fMfsVec.size()), msg, plProgressMgr::kNone, false, true); |
|
|
|
|
const NetCliFileManifestEntry mfs = manifest[i]; |
|
|
|
|
char* fileName = hsWStringToString(mfs.clientName); |
|
|
|
|
|
|
|
|
|
UInt32 downloadSize = 0; |
|
|
|
|
UInt32 downloadFiles = 0; |
|
|
|
|
for (MfsFileVec::iterator i = fMfsVec.begin(); i != fMfsVec.end(); ++i) |
|
|
|
|
{ |
|
|
|
|
plManifestFile* mfsFile = (*i); |
|
|
|
|
|
|
|
|
|
if (!mfsFile->IsLocalUpToDate()) |
|
|
|
|
// See if the files are the same
|
|
|
|
|
// 1. Check file size before we do time consuming md5 operations
|
|
|
|
|
// 2. Do wasteful md5. We should consider implementing a CRC instead.
|
|
|
|
|
if (plFileUtils::GetFileSize(fileName) == mfs.fileSize) |
|
|
|
|
{ |
|
|
|
|
downloadFiles++; |
|
|
|
|
downloadSize += mfsFile->GetDownloadSize(); |
|
|
|
|
} |
|
|
|
|
plMD5Checksum cliMD5(fileName); |
|
|
|
|
plMD5Checksum srvMD5; |
|
|
|
|
char* eapSucksString = hsWStringToString(mfs.md5); |
|
|
|
|
srvMD5.SetFromHexString(eapSucksString); |
|
|
|
|
delete[] eapSucksString; |
|
|
|
|
|
|
|
|
|
progress->Increment(1.f); |
|
|
|
|
if (cliMD5 == srvMD5) |
|
|
|
|
{ |
|
|
|
|
delete[] fileName; |
|
|
|
|
continue; |
|
|
|
|
} else |
|
|
|
|
PatcherLog(kInfo, " Enqueueing %s: MD5 Checksums Differ", fileName); |
|
|
|
|
} else |
|
|
|
|
PatcherLog(kInfo, " Enqueueing %s: File Sizes Differ", fileName); |
|
|
|
|
|
|
|
|
|
// If we're still here, then we need to update the file.
|
|
|
|
|
patcher->GetProgress()->SetLength((hsScalar)mfs.fileSize + patcher->GetProgress()->GetMax()); |
|
|
|
|
patcher->RequestFile(mfs.downloadName, mfs.clientName); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
delete progress; |
|
|
|
|
|
|
|
|
|
PatcherLog(kInfo, "Got download stats, %d files, %d bytes", downloadFiles, downloadSize); |
|
|
|
|
|
|
|
|
|
return downloadSize; |
|
|
|
|
patcher->IssueRequest(); |
|
|
|
|
delete[] name; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool plResPatcher::CheckFreeSpace(UInt32 bytesNeeded) |
|
|
|
|
{ |
|
|
|
|
#ifdef HS_BUILD_FOR_WIN32 |
|
|
|
|
ULARGE_INTEGER freeBytesAvailable, totalNumberOfBytes, neededBytes; |
|
|
|
|
if (GetDiskFreeSpaceEx(NULL, &freeBytesAvailable, &totalNumberOfBytes, NULL)) |
|
|
|
|
{ |
|
|
|
|
neededBytes.HighPart = 0; |
|
|
|
|
neededBytes.LowPart = bytesNeeded; |
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
if (neededBytes.QuadPart > freeBytesAvailable.QuadPart) |
|
|
|
|
{ |
|
|
|
|
PatcherLog(kInfo, "Not enough disk space (asked for %d bytes)", bytesNeeded); |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
#endif // HS_BUILD_FOR_WIN32
|
|
|
|
|
static char* sLastError = nil; |
|
|
|
|
plResPatcher* plResPatcher::fInstance = nil; |
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
|
plResPatcher* plResPatcher::GetInstance() |
|
|
|
|
{ |
|
|
|
|
if (!fInstance) |
|
|
|
|
fInstance = new plResPatcher; |
|
|
|
|
return fInstance; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool plResPatcher::IDecompressSound(plManifestFile* mfsFile, bool noOverwrite) |
|
|
|
|
void plResPatcher::Shutdown() |
|
|
|
|
{ |
|
|
|
|
UInt32 flags = mfsFile->GetFlags(); |
|
|
|
|
|
|
|
|
|
if ( (hsCheckBits(flags, plManifestFile::kSndFlagCacheSplit) || hsCheckBits(flags, plManifestFile::kSndFlagCacheStereo)) && stricmp(plFileUtils::GetFileExt(mfsFile->GetName()), "ogg") == 0) |
|
|
|
|
{ |
|
|
|
|
plAudioFileReader* reader = plAudioFileReader::CreateReader(mfsFile->GetName(), plAudioCore::kAll, plAudioFileReader::kStreamNative); |
|
|
|
|
if (!reader) |
|
|
|
|
{ |
|
|
|
|
PatcherLog(kInfo, "Unable to create audio file reader for %s", mfsFile->GetName()); |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
UInt32 size = reader->GetDataSize(); |
|
|
|
|
delete reader; |
|
|
|
|
// Better not call this while we're patching
|
|
|
|
|
delete fInstance; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Make sure we have enough free space
|
|
|
|
|
if (!CheckFreeSpace(size)) |
|
|
|
|
return false; |
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
if (hsCheckBits(flags, plManifestFile::kSndFlagCacheSplit)) |
|
|
|
|
plAudioFileReader::CacheFile(mfsFile->GetName(), true, noOverwrite); |
|
|
|
|
if (hsCheckBits(flags, plManifestFile::kSndFlagCacheStereo)) |
|
|
|
|
plAudioFileReader::CacheFile(mfsFile->GetName(), false, noOverwrite); |
|
|
|
|
} |
|
|
|
|
plResPatcher::plResPatcher() |
|
|
|
|
: fPatching(false), fProgress(nil) { } |
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
|
plResPatcher::~plResPatcher() |
|
|
|
|
{ |
|
|
|
|
if (fProgress) |
|
|
|
|
delete fProgress; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool plResPatcher::Update() |
|
|
|
|
void plResPatcher::IssueRequest() |
|
|
|
|
{ |
|
|
|
|
UInt32 downloadSize = IGetDownloadSize(); |
|
|
|
|
// if download size is 0, nothing to download, but we still need to tell the res manager about the files
|
|
|
|
|
|
|
|
|
|
plFileUtils::CreateDir("dat"); |
|
|
|
|
plFileUtils::CreateDir("sfx"); |
|
|
|
|
|
|
|
|
|
if (!CheckFreeSpace(downloadSize)) |
|
|
|
|
return false; |
|
|
|
|
|
|
|
|
|
#ifdef PLASMA_EXTERNAL_RELEASE |
|
|
|
|
bool showAgeName = fAlwaysShowAgeName; |
|
|
|
|
#else |
|
|
|
|
bool showAgeName = true; |
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
char msg[128]; |
|
|
|
|
if (!fAgeToPatch.empty()) |
|
|
|
|
{ |
|
|
|
|
if (showAgeName) |
|
|
|
|
sprintf(msg, "Downloading %s data...", fAgeToPatch.c_str()); |
|
|
|
|
else |
|
|
|
|
strcpy(msg, "Downloading age data..."); |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
sprintf(msg, "Downloading..."); |
|
|
|
|
|
|
|
|
|
plOperationProgress* progress = plProgressMgr::GetInstance()->RegisterOverallOperation((hsScalar)downloadSize, msg, plProgressMgr::kUpdateText, true); |
|
|
|
|
|
|
|
|
|
bool result = true; |
|
|
|
|
plResManager* resMgr = ((plResManager*)hsgResMgr::ResMgr()); |
|
|
|
|
|
|
|
|
|
for (MfsFileVec::iterator i = fMfsVec.begin(); i != fMfsVec.end(); ++i) |
|
|
|
|
{ |
|
|
|
|
plManifestFile* mfsFile = (*i); |
|
|
|
|
|
|
|
|
|
if (!mfsFile->IsLocalUpToDate()) |
|
|
|
|
{ |
|
|
|
|
FileType type = IGetFile(mfsFile, progress); |
|
|
|
|
if (type == kPrp) |
|
|
|
|
{ |
|
|
|
|
// Checks for existence before attempting to remove
|
|
|
|
|
resMgr->RemoveSinglePage(mfsFile->GetName()); |
|
|
|
|
} |
|
|
|
|
else if (type == kOther) |
|
|
|
|
{ |
|
|
|
|
if (!IDecompressSound(mfsFile, false)) |
|
|
|
|
{ |
|
|
|
|
char text[MAX_PATH]; |
|
|
|
|
StrPrintf(text, arrsize(text), "%s could not be decompressed", mfsFile->GetName()); |
|
|
|
|
PatcherLog(kInfo, text ); |
|
|
|
|
hsAssert(false, text); |
|
|
|
|
result = false; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
char text[MAX_PATH]; |
|
|
|
|
StrPrintf(text, arrsize(text), "Failed downloading file: %s", mfsFile->GetName()); |
|
|
|
|
PatcherLog(kInfo, text ); |
|
|
|
|
hsAssert(false, text); |
|
|
|
|
result = false; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
if (!fPatching) return; |
|
|
|
|
if (fRequests.empty()) |
|
|
|
|
// Wheee!
|
|
|
|
|
Finish(); |
|
|
|
|
else { |
|
|
|
|
Request req = fRequests.front(); |
|
|
|
|
fRequests.pop(); |
|
|
|
|
|
|
|
|
|
std::wstring title; |
|
|
|
|
if (req.fType == kManifest) |
|
|
|
|
{ |
|
|
|
|
if (!IDecompressSound(mfsFile, true)) |
|
|
|
|
{ |
|
|
|
|
char text[MAX_PATH]; |
|
|
|
|
StrPrintf(text, arrsize(text), "%s could not be decompressed", mfsFile->GetName()); |
|
|
|
|
PatcherLog(kInfo, text ); |
|
|
|
|
hsAssert(false, text); |
|
|
|
|
result = false; |
|
|
|
|
char* eapSucksString = hsWStringToString(req.fFile.c_str()); |
|
|
|
|
PatcherLog(kMajorStatus, " Downloading manifest... %s", eapSucksString); |
|
|
|
|
xtl::format(title, L"Checking %s for updates...", req.fFile.c_str()); |
|
|
|
|
NetCliFileManifestRequest(ManifestDownloaded, this, req.fFile.c_str()); |
|
|
|
|
delete[] eapSucksString; |
|
|
|
|
} else if (req.fType == kFile) { |
|
|
|
|
char* eapSucksString = hsWStringToString(req.fFriendlyName.c_str()); |
|
|
|
|
PatcherLog(kMajorStatus, " Downloading file... %s", eapSucksString); |
|
|
|
|
xtl::format(title, L"Downloading... %s", plFileUtils::GetFileName(req.fFriendlyName.c_str())); |
|
|
|
|
|
|
|
|
|
plFileUtils::EnsureFilePathExists(req.fFriendlyName.c_str()); |
|
|
|
|
plResDownloadStream* stream = new plResDownloadStream(fProgress, req.fFile.c_str()); |
|
|
|
|
if(stream->Open(eapSucksString, "wb")) |
|
|
|
|
NetCliFileDownloadRequest(req.fFile.c_str(), stream, FileDownloaded, this); |
|
|
|
|
else { |
|
|
|
|
PatcherLog(kError, " Unable to create file %s", eapSucksString); |
|
|
|
|
Finish(false); |
|
|
|
|
} |
|
|
|
|
delete[] eapSucksString; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!resMgr->FindSinglePage(mfsFile->GetName()) && stricmp(plFileUtils::GetFileExt(mfsFile->GetName()), "prp") == 0) |
|
|
|
|
{ |
|
|
|
|
resMgr->AddSinglePage(mfsFile->GetName()); |
|
|
|
|
} |
|
|
|
|
char* hack = hsWStringToString(title.c_str()); |
|
|
|
|
fProgress->SetTitle(hack); |
|
|
|
|
delete[] hack; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
PatcherLog(kMajorStatus, "Cleaning up patcher..." ); |
|
|
|
|
delete progress; |
|
|
|
|
|
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
plResPatcher::FileType plResPatcher::IGetFile(const plManifestFile* mfsFile, plOperationProgress* progressBar) |
|
|
|
|
void plResPatcher::Finish(bool success) |
|
|
|
|
{ |
|
|
|
|
PatcherLog(kInfo, " Setting up to download file %s", mfsFile->GetName()); |
|
|
|
|
while (fRequests.size()) |
|
|
|
|
fRequests.pop(); |
|
|
|
|
if (fProgress) { |
|
|
|
|
delete fProgress; |
|
|
|
|
fProgress = nil; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool downloadDone = false; |
|
|
|
|
wchar* wServerPath = hsStringToWString(mfsFile->GetServerPath()); |
|
|
|
|
int numTries = 0; |
|
|
|
|
fPatching = false; |
|
|
|
|
if (success) |
|
|
|
|
PatcherLog(kHeader, "--- Patch Completed Successfully ---"); |
|
|
|
|
else |
|
|
|
|
PatcherLog(kHeader, "--- Patch Killed by Error ---"); |
|
|
|
|
|
|
|
|
|
while (!downloadDone) |
|
|
|
|
plResPatcherMsg* pMsg = new plResPatcherMsg(success, sLastError); |
|
|
|
|
pMsg->Send(); // whoosh... off it goes
|
|
|
|
|
if (sLastError) |
|
|
|
|
{ |
|
|
|
|
if (numTries >= kMaxDownloadTries) |
|
|
|
|
{ |
|
|
|
|
PatcherLog(kInfo, " Max download tries exceeded (%d). Aborting download...", kMaxDownloadTries); |
|
|
|
|
return kFail; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
plDownloadStream downloadStream(progressBar); |
|
|
|
|
if (!downloadStream.Open(mfsFile->GetName(), "wb")) |
|
|
|
|
{ |
|
|
|
|
PatcherLog(kInfo, " Unable to create file. Aborting download..."); |
|
|
|
|
return kFail; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
PatcherLog(kInfo, " Downloading file %s...", mfsFile->GetName()); |
|
|
|
|
|
|
|
|
|
fSuccess = false;
|
|
|
|
|
fDoneWithFile = false; |
|
|
|
|
NetCliFileDownloadRequest( |
|
|
|
|
wServerPath, |
|
|
|
|
&downloadStream, |
|
|
|
|
DownloadFileCallback, |
|
|
|
|
this |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
while (!fDoneWithFile) { |
|
|
|
|
NetClientUpdate(); |
|
|
|
|
plgDispatch::Dispatch()->MsgQueueProcess(); |
|
|
|
|
AsyncSleep(10); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!fSuccess) { |
|
|
|
|
// remove partial file and die (server didn't have the file or server is shutting down)
|
|
|
|
|
downloadStream.RewindProgress(); |
|
|
|
|
plFileUtils::RemoveFile(mfsFile->GetName(), true); |
|
|
|
|
PatcherLog(kError, " File %s failed to download.", mfsFile->GetName()); |
|
|
|
|
downloadDone = true; |
|
|
|
|
} |
|
|
|
|
else { |
|
|
|
|
if (downloadStream.DecompressedOk()) { |
|
|
|
|
PatcherLog(kInfo, " Decompress successful." ); |
|
|
|
|
// download and decompress successful, do a md5 check on the resulting file
|
|
|
|
|
plMD5Checksum localMD5(mfsFile->GetName()); |
|
|
|
|
if (localMD5 != mfsFile->GetChecksum()) { |
|
|
|
|
downloadStream.RewindProgress(); |
|
|
|
|
downloadStream.Close(); |
|
|
|
|
plFileUtils::RemoveFile(mfsFile->GetName(), true); |
|
|
|
|
PatcherLog(kError, " File %s MD5 check FAILED.", mfsFile->GetName()); |
|
|
|
|
// don't set downloadDone so we attempt to re-download from the server
|
|
|
|
|
} |
|
|
|
|
else { |
|
|
|
|
downloadStream.Close(); |
|
|
|
|
PatcherLog(kInfo, " MD5 check succeeded."); |
|
|
|
|
downloadDone = true; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
else { |
|
|
|
|
downloadStream.RewindProgress(); |
|
|
|
|
downloadStream.Close(); |
|
|
|
|
plFileUtils::RemoveFile(mfsFile->GetName(), true); |
|
|
|
|
PatcherLog(kError, " File %s failed to decompress.", mfsFile->GetName()); |
|
|
|
|
// don't set downloadDone so we attempt to re-download from the server
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
++numTries; |
|
|
|
|
delete[] sLastError; |
|
|
|
|
sLastError = nil; |
|
|
|
|
} |
|
|
|
|
FREE(wServerPath); |
|
|
|
|
|
|
|
|
|
if (!fSuccess)
|
|
|
|
|
return kFail; |
|
|
|
|
|
|
|
|
|
if (stricmp(plFileUtils::GetFileExt(mfsFile->GetName()), "prp") == 0) |
|
|
|
|
return kPrp; |
|
|
|
|
|
|
|
|
|
return kOther; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool plResPatcher::IGetAgeManifest() |
|
|
|
|
void plResPatcher::RequestFile(const wchar_t* srvName, const wchar_t* cliName) |
|
|
|
|
{ |
|
|
|
|
if (fMfsVec.size() > 0) |
|
|
|
|
return true; |
|
|
|
|
|
|
|
|
|
PatcherLog(kMajorStatus, "Downloading new manifest from data server..." ); |
|
|
|
|
|
|
|
|
|
fSuccess = false; |
|
|
|
|
wchar* group = hsStringToWString(fAgeToPatch.c_str()); |
|
|
|
|
unsigned numTries = 0; |
|
|
|
|
while (!fSuccess) |
|
|
|
|
{ |
|
|
|
|
numTries++; |
|
|
|
|
fDoneWithFile = false; |
|
|
|
|
NetCliFileManifestRequest(ManifestCallback, this, group); |
|
|
|
|
while (!fDoneWithFile) |
|
|
|
|
{ |
|
|
|
|
NetClientUpdate(); |
|
|
|
|
plgDispatch::Dispatch()->MsgQueueProcess(); |
|
|
|
|
AsyncSleep(10); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!fSuccess) |
|
|
|
|
{ |
|
|
|
|
fMfsVec.clear(); // clear out any bad data
|
|
|
|
|
if (numTries > kMaxDownloadTries) |
|
|
|
|
break; // abort
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
delete [] group; |
|
|
|
|
|
|
|
|
|
if (fSuccess) |
|
|
|
|
PatcherLog(kStatus, "New age manifest read; number of files: %d", fMfsVec.size() ); |
|
|
|
|
else |
|
|
|
|
PatcherLog(kStatus, "Failed to download manifest after trying %d times", kMaxDownloadTries); |
|
|
|
|
|
|
|
|
|
return fSuccess; |
|
|
|
|
fRequests.push(Request(srvName, kFile, cliName)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void plResPatcher::DoneWithManifest(bool success, const NetCliFileManifestEntry manifestEntires[], unsigned entryCount) |
|
|
|
|
void plResPatcher::RequestManifest(const wchar_t* age) |
|
|
|
|
{ |
|
|
|
|
PatcherLog(kStatus, "New age manifest received. Reading..."); |
|
|
|
|
|
|
|
|
|
if (success) |
|
|
|
|
{ |
|
|
|
|
for (unsigned i = 0; i < entryCount; i++) |
|
|
|
|
{ |
|
|
|
|
char* name = hsWStringToString(manifestEntires[i].clientName); |
|
|
|
|
char* serverPath = hsWStringToString(manifestEntires[i].downloadName); |
|
|
|
|
char* md5Str = hsWStringToString(manifestEntires[i].md5); |
|
|
|
|
int size = manifestEntires[i].fileSize; |
|
|
|
|
int zipsize = manifestEntires[i].zipSize; |
|
|
|
|
int flags = manifestEntires[i].flags; |
|
|
|
|
if (stricmp(plFileUtils::GetFileExt(name), "gz")) |
|
|
|
|
flags |= plManifestFile::kFlagZipped; // add zipped flag if necessary
|
|
|
|
|
|
|
|
|
|
plMD5Checksum sum; |
|
|
|
|
sum.SetFromHexString(md5Str); |
|
|
|
|
fMfsVec.push_back(TRACKED_NEW plManifestFile(name, serverPath, sum, size, zipsize, flags)); |
|
|
|
|
|
|
|
|
|
delete [] name; |
|
|
|
|
delete [] serverPath; |
|
|
|
|
delete [] md5Str; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
fRequests.push(Request(age, kManifest)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fDoneWithFile = true; |
|
|
|
|
fSuccess = success; |
|
|
|
|
void plResPatcher::Start() |
|
|
|
|
{ |
|
|
|
|
hsAssert(!fPatching, "Too many calls to plResPatcher::Start"); |
|
|
|
|
fPatching = true; |
|
|
|
|
PatcherLog(kHeader, "--- Patch Started (%i requests) ---", fRequests.size()); |
|
|
|
|
fProgress = plProgressMgr::GetInstance()->RegisterOperation(0.0, "Checking for updates...", |
|
|
|
|
plProgressMgr::kUpdateText, false, true); |
|
|
|
|
IssueRequest(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
void PatcherLog(PatcherLogType type, const char* format, ...) |
|
|
|
|
{ |
|
|
|
|
UInt32 color = 0; |
|
|
|
@ -512,7 +302,13 @@ void PatcherLog(PatcherLogType type, const char* format, ...)
|
|
|
|
|
va_list args; |
|
|
|
|
va_start(args, format); |
|
|
|
|
|
|
|
|
|
gStatusLog->AddLineV(color, format, args); |
|
|
|
|
if (type == kError) |
|
|
|
|
{ |
|
|
|
|
sLastError = new char[1024]; // Deleted by Finish(false)
|
|
|
|
|
vsprintf(sLastError, format, args); |
|
|
|
|
gStatusLog->AddLine(sLastError, color); |
|
|
|
|
} else |
|
|
|
|
gStatusLog->AddLineV(color, format, args); |
|
|
|
|
|
|
|
|
|
va_end(args); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|