Browse Source

Redist Updating

This is the crowning feature: the whole point of this exercise. You can
now update redists from the client launcher itself. To activate this
functionality, you will need to flag the file 0x10 in the manifest. I
recommend listing your redists in the patcher manifests.
Adam Johnson 11 years ago
parent
commit
1bcd17c850
  1. 82
      Sources/Plasma/Apps/plUruLauncher/plClientLauncher.cpp
  2. 15
      Sources/Plasma/Apps/plUruLauncher/plClientLauncher.h
  3. 145
      Sources/Plasma/Apps/plUruLauncher/winmain.cpp
  4. 16
      Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.cpp
  5. 6
      Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.h

82
Sources/Plasma/Apps/plUruLauncher/plClientLauncher.cpp

@ -58,7 +58,9 @@ Mead, WA 99021
#include "pfConsoleCore/pfConsoleEngine.h"
PF_CONSOLE_LINK_FILE(Core)
#include <algorithm>
#include <curl/curl.h>
#include <deque>
plClientLauncher::ErrorFunc s_errorProc = nullptr; // don't even ask, cause I'm not happy about this.
@ -145,12 +147,69 @@ void plShardStatus::Update()
// ===================================================
class plRedistUpdater : public hsThread
{
bool fSuccess;
public:
plClientLauncher* fParent;
plClientLauncher::InstallRedistFunc fInstallProc;
std::deque<plFileName> fRedistQueue;
plRedistUpdater()
: fSuccess(true)
{ }
~plRedistUpdater()
{
// If anything is left in the deque, it was not installed.
// We should unlink them so the next launch will redownload and install them.
std::for_each(fRedistQueue.begin(), fRedistQueue.end(),
[] (const plFileName& file) {
plFileSystem::Unlink(file);
}
);
}
virtual void OnQuit()
{
// If we succeeded, then we should launch the game client...
if (fSuccess)
fParent->LaunchClient();
}
virtual hsError Run()
{
while (!fRedistQueue.empty()) {
if (fInstallProc(fRedistQueue.back()))
fRedistQueue.pop_back();
else {
s_errorProc(kNetErrInternalError, fRedistQueue.back().AsString());
fSuccess = false;
return hsFail;
}
}
return hsOK;
}
virtual void Start()
{
if (fRedistQueue.empty())
OnQuit();
else
hsThread::Start();
}
};
// ===================================================
plClientLauncher::plClientLauncher() :
fFlags(0),
fServerIni("server.ini"),
fPatcherFactory(nullptr),
fClientExecutable(plManifest::ClientExecutable()),
fStatusThread(new plShardStatus())
fStatusThread(new plShardStatus()),
fInstallerThread(new plRedistUpdater())
{
pfPatcher::GetLog()->AddLine(plProduct::ProductString().c_str());
}
@ -188,9 +247,11 @@ void plClientLauncher::IOnPatchComplete(ENetError result, const plString& msg)
// case 1
hsSetBits(fFlags, kHaveSelfPatched);
PatchClient();
} else
// cases 2 & 3
fLaunchClientFunc(fClientExecutable, GetAppArgs());
} else {
// cases 2 & 3 -- update any redistributables, then launch the client.
fInstallerThread->fParent = this;
fInstallerThread->Start();
}
} else if (s_errorProc)
s_errorProc(result, msg);
}
@ -203,6 +264,13 @@ bool plClientLauncher::IApproveDownload(const plFileName& file)
return !path.AsString().IsEmpty();
}
void plClientLauncher::LaunchClient() const
{
if (fStatusFunc)
fStatusFunc("Launching...");
fLaunchClientFunc(fClientExecutable, GetAppArgs());
}
void plClientLauncher::PatchClient()
{
if (fStatusFunc) {
@ -216,6 +284,7 @@ void plClientLauncher::PatchClient()
pfPatcher* patcher = fPatcherFactory();
patcher->OnCompletion(std::bind(&plClientLauncher::IOnPatchComplete, this, std::placeholders::_1, std::placeholders::_2));
patcher->OnSelfPatch([&](const plFileName& file) { fClientExecutable = file; });
patcher->OnRedistUpdate([&](const plFileName& file) { fInstallerThread->fRedistQueue.push_back(file); });
// If this is a repair, we need to approve the downloads...
if (hsCheckBits(fFlags, kGameDataOnly))
@ -388,6 +457,11 @@ void plClientLauncher::SetErrorProc(ErrorFunc proc)
s_errorProc = proc;
}
void plClientLauncher::SetInstallerProc(InstallRedistFunc proc)
{
fInstallerThread->fInstallProc = proc;
}
void plClientLauncher::SetShardProc(StatusFunc proc)
{
fStatusThread->fShardFunc = proc;

15
Sources/Plasma/Apps/plUruLauncher/plClientLauncher.h

@ -54,6 +54,7 @@ class plClientLauncher
public:
typedef std::function<class pfPatcher*(void)> CreatePatcherFunc;
typedef std::function<void(ENetError, const plString&)> ErrorFunc;
typedef std::function<bool(const plFileName&)> InstallRedistFunc;
typedef std::function<void(const plFileName&, const plString&)> LaunchClientFunc;
typedef std::function<void(const plString&)> StatusFunc;
@ -71,7 +72,9 @@ private:
plFileName fServerIni;
plFileName fClientExecutable;
std::unique_ptr<class plShardStatus> fStatusThread;
std::unique_ptr<class plShardStatus> fStatusThread;
std::unique_ptr<class plRedistUpdater> fInstallerThread;
CreatePatcherFunc fPatcherFactory;
LaunchClientFunc fLaunchClientFunc;
@ -86,6 +89,11 @@ public:
plClientLauncher();
~plClientLauncher();
/** Launch whatever client we think is appropriate. Please note that you should not call this unless you know
* absolutely without question what you are doing!
*/
void LaunchClient() const;
/** Begin the next logical patch operation. We are internally tracking if this is a self patch or a client patch.
* All you need to do is make certain the doggone callbacks are set so that your UI will update. In theory, you
* should never call this from your UI code since we manage this state for you.
@ -129,6 +137,11 @@ public:
*/
void SetErrorProc(ErrorFunc proc);
/** Set a callback that will execute and wait for redistributable installers.
* \remarks This will be called from a worker thread.
*/
void SetInstallerProc(InstallRedistFunc proc);
/** Set a patcher factory. */
void SetPatcherFactory(CreatePatcherFunc factory) { fPatcherFactory = factory; }

145
Sources/Plasma/Apps/plUruLauncher/winmain.cpp

@ -52,6 +52,7 @@ Mead, WA 99021
#include "hsWindows.h"
#include "resource.h"
#include <commctrl.h>
#include <shellapi.h>
#include <shlobj.h>
// ===================================================
@ -207,6 +208,108 @@ static void IOnProgressTick(uint64_t curBytes, uint64_t totalBytes, const plStri
// ===================================================
static void ISetDownloadStatus(const plString& status)
{
SetDlgItemTextW(s_dialog, IDC_TEXT, status.ToWchar());
// consider this a reset of the download status...
IShowMarquee();
SetDlgItemTextW(s_dialog, IDC_DLSIZE, L"");
SetDlgItemTextW(s_dialog, IDC_DLSPEED, L"");
if (s_taskbar)
s_taskbar->SetProgressState(s_dialog, TBPF_INDETERMINATE);
}
static HANDLE ICreateProcess(const plFileName& exe, const plString& args)
{
STARTUPINFOW si;
PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si));
memset(&pi, 0, sizeof(pi));
si.cb = sizeof(si);
// Create wchar things and stuff :/
plString cmd = plString::Format("%s %s", exe.AsString().c_str(), args.c_str());
plStringBuffer<wchar_t> file = exe.AsString().ToWchar();
plStringBuffer<wchar_t> params = cmd.ToWchar();
// Guess what? CreateProcess isn't smart enough to throw up an elevation dialog... We need ShellExecute for that.
// But guess what? ShellExecute won't run ".exe.tmp" files. GAAAAAAAAHHHHHHHHH!!!!!!!
BOOL result = CreateProcessW(
file,
const_cast<wchar_t*>(params.GetData()),
nullptr,
nullptr,
FALSE,
DETACHED_PROCESS,
nullptr,
nullptr,
&si,
&pi
);
// So maybe it needs elevation... Or maybe everything arseploded.
if (result != FALSE) {
CloseHandle(pi.hThread);
return pi.hProcess;
} else if (GetLastError() == ERROR_ELEVATION_REQUIRED) {
SHELLEXECUTEINFOW info;
memset(&info, 0, sizeof(info));
info.cbSize = sizeof(info);
info.lpFile = file.GetData();
info.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NOASYNC;
info.lpParameters = args.ToWchar();
hsAssert(ShellExecuteExW(&info), "ShellExecuteExW phailed");
return info.hProcess;
} else {
wchar_t buf[2048];
FormatMessageW(
FORMAT_MESSAGE_FROM_SYSTEM,
nullptr,
GetLastError(),
LANG_USER_DEFAULT,
buf,
arrsize(buf),
nullptr
);
hsMessageBox(buf, L"Error", hsMessageBoxNormal, hsMessageBoxIconError);
}
return nullptr;
}
static bool IInstallRedist(const plFileName& exe)
{
ISetDownloadStatus(plString::Format("Installing... %s", exe.AsString().c_str()));
Sleep(2500); // let's Sleep for a bit so the user can see that we're doing something before the UAC dialog pops up!
// Try to guess some arguments... Unfortunately, the file manifest format is fairly immutable.
plStringStream ss;
if (exe.AsString().CompareI("oalinst.exe") == 0)
ss << "/s"; // rarg nonstandard
else
ss << "/q";
if (exe.AsString().Find("vcredist", plString::kCaseInsensitive) != -1)
ss << " /norestart"; // I don't want to image the accusations of viruses and hacking if this happened...
// Now fire up the process...
HANDLE process = ICreateProcess(exe, ss.GetString());
if (process) {
WaitForSingleObject(process, INFINITE);
// Get the exit code so we can indicate success/failure to the redist thread
DWORD code = PLASMA_OK;
hsAssert(GetExitCodeProcess(process, &code), "failed to get redist exit code");
CloseHandle(process);
return code != PLASMA_PHAILURE;
}
return PLASMA_PHAILURE;
}
static void ILaunchClientExecutable(const plFileName& exe, const plString& args)
{
// Once we start launching something, we no longer need to trumpet any taskbar status
@ -216,38 +319,16 @@ static void ILaunchClientExecutable(const plFileName& exe, const plString& args)
// Only launch a client executable if we're given one. If not, that's probably a cue that we're
// done with some service operation and need to go away.
if (!exe.AsString().IsEmpty()) {
STARTUPINFOW si;
PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si));
memset(&pi, 0, sizeof(pi));
si.cb = sizeof(si);
// This event will prevent the game from restarting the patcher
HANDLE hEvent = CreateEventW(nullptr, TRUE, FALSE, L"UruPatcherEvent");
// Fire up ye olde new process
plString cmd = plString::Format("%s %s", exe.AsString().c_str(), args.c_str());
CreateProcessW(
exe.AsString().ToWchar(),
const_cast<wchar_t*>(cmd.ToWchar().GetData()), // windows claims that it may modify this... let's hope that doesn't happen.
nullptr,
nullptr,
FALSE,
DETACHED_PROCESS,
nullptr,
plFileSystem::GetCWD().AsString().ToWchar(),
&si,
&pi
);
HANDLE process = ICreateProcess(exe, args);
// if this is the real game client, then we need to make sure it gets this event...
if (plManifest::ClientExecutable().AsString().CompareI(exe.AsString()) == 0) {
WaitForInputIdle(pi.hProcess, 1000);
WaitForInputIdle(process, 1000);
WaitForSingleObject(hEvent, INFINITE);
}
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
CloseHandle(process);
CloseHandle(hEvent);
}
@ -265,19 +346,6 @@ static void IOnNetError(ENetError result, const plString& msg)
IQuit(PLASMA_PHAILURE);
}
static void ISetDownloadStatus(const plString& status)
{
SetDlgItemTextW(s_dialog, IDC_TEXT, status.ToWchar());
// consider this a reset of the download status...
IShowMarquee();
SetDlgItemTextW(s_dialog, IDC_DLSIZE, L"");
SetDlgItemTextW(s_dialog, IDC_DLSPEED, L"");
if (s_taskbar)
s_taskbar->SetProgressState(s_dialog, TBPF_INDETERMINATE);
}
static void ISetShardStatus(const plString& status)
{
SetDlgItemTextW(s_dialog, IDC_STATUS_TEXT, status.ToWchar());
@ -299,6 +367,7 @@ int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdL
// Let's initialize our plClientLauncher friend
s_launcher.ParseArguments();
s_launcher.SetErrorProc(IOnNetError);
s_launcher.SetInstallerProc(IInstallRedist);
s_launcher.SetLaunchClientProc(ILaunchClientExecutable);
s_launcher.SetPatcherFactory(IPatcherFactory);
s_launcher.SetShardProc(ISetShardStatus);

16
Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.cpp

@ -90,9 +90,12 @@ struct pfPatcherWorker : public hsThread
// Any file
kFlagZipped = 1<<3,
// Executable flags
kRedistUpdate = 1<<4,
// Begin internal flags
kLastManifestFlag = 1<<4,
kSelfPatch = 1<<5,
kLastManifestFlag = 1<<5,
kSelfPatch = 1<<6,
};
std::deque<Request> fRequests;
@ -108,6 +111,7 @@ struct pfPatcherWorker : public hsThread
pfPatcher::FileDownloadFunc fFileDownloaded;
pfPatcher::GameCodeDiscoverFunc fGameCodeDiscovered;
pfPatcher::ProgressTickFunc fProgressTick;
pfPatcher::FileDownloadFunc fRedistUpdateDownloaded;
pfPatcher::FileDownloadFunc fSelfPatch;
pfPatcher* fParent;
@ -205,6 +209,7 @@ public:
void Begin() { fDLStartTime = hsTimer::GetSysSeconds(); }
plFileName GetFileName() const { return fFilename; }
bool IsRedistUpdate() const { return hsCheckBits(fFlags, pfPatcherWorker::kRedistUpdate); }
bool IsSelfPatch() const { return hsCheckBits(fFlags, pfPatcherWorker::kSelfPatch); }
void Unlink() const { plFileSystem::Unlink(fFilename); }
};
@ -315,6 +320,8 @@ static void IFileThingDownloadCB(ENetError result, void* param, const plFileName
patcher->WhitelistFile(stream->GetFileName(), true);
if (patcher->fSelfPatch && stream->IsSelfPatch())
patcher->fSelfPatch(stream->GetFileName());
if (patcher->fRedistUpdateDownloaded && stream->IsRedistUpdate())
patcher->fRedistUpdateDownloaded(stream->GetFileName());
patcher->IssueRequest();
} else {
PatcherLogRed("\tDownloaded Failed: File '%s'", stream->GetFileName().AsString().c_str());
@ -594,6 +601,11 @@ void pfPatcher::OnProgressTick(ProgressTickFunc cb)
fWorker->fProgressTick = cb;
}
void pfPatcher::OnRedistUpdate(FileDownloadFunc cb)
{
fWorker->fRedistUpdateDownloaded = cb;
}
void pfPatcher::OnSelfPatch(FileDownloadFunc cb)
{
fWorker->fSelfPatch = cb;

6
Sources/Plasma/FeatureLib/pfPatcher/pfPatcher.h

@ -121,6 +121,12 @@ public:
*/
void OnProgressTick(ProgressTickFunc cb);
/** Set a callback that will be fired when the patcher downloads an updated redistributable. Such as
* the Visual C++ runtime (vcredist_x86.exe). You are responsible for installing it.
* \remarks This will be called from the network thread.
*/
void OnRedistUpdate(FileDownloadFunc cb);
/** This is called when the current application has been updated. */
void OnSelfPatch(FileDownloadFunc cb);

Loading…
Cancel
Save