2022-08-22 22:21:23 +02:00
|
|
|
#include "gui/CemuUpdateWindow.h"
|
|
|
|
|
|
|
|
#include "Common/version.h"
|
|
|
|
#include "util/helpers/helpers.h"
|
|
|
|
#include "util/helpers/SystemException.h"
|
|
|
|
#include "config/ActiveSettings.h"
|
2022-09-07 02:42:25 +02:00
|
|
|
#include "Common/FileStream.h"
|
2022-08-22 22:21:23 +02:00
|
|
|
|
|
|
|
#include <wx/sizer.h>
|
|
|
|
#include <wx/gauge.h>
|
|
|
|
#include <wx/button.h>
|
|
|
|
#include <wx/msgdlg.h>
|
|
|
|
|
|
|
|
#include <curl/curl.h>
|
|
|
|
#include <zip.h>
|
|
|
|
#include <boost/tokenizer.hpp>
|
|
|
|
|
|
|
|
|
|
|
|
wxDECLARE_EVENT(wxEVT_RESULT, wxCommandEvent);
|
|
|
|
wxDEFINE_EVENT(wxEVT_RESULT, wxCommandEvent);
|
|
|
|
|
|
|
|
wxDECLARE_EVENT(wxEVT_PROGRESS, wxCommandEvent);
|
|
|
|
wxDEFINE_EVENT(wxEVT_PROGRESS, wxCommandEvent);
|
|
|
|
|
|
|
|
CemuUpdateWindow::CemuUpdateWindow(wxWindow* parent)
|
|
|
|
: wxDialog(parent, wxID_ANY, "Cemu update", wxDefaultPosition, wxDefaultSize,
|
2022-08-31 12:04:09 +02:00
|
|
|
wxCAPTION | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX)
|
2022-08-22 22:21:23 +02:00
|
|
|
{
|
|
|
|
auto* sizer = new wxBoxSizer(wxVERTICAL);
|
|
|
|
m_gauge = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxSize(500, 20), wxGA_HORIZONTAL);
|
|
|
|
m_gauge->SetValue(0);
|
|
|
|
sizer->Add(m_gauge, 0, wxALL | wxEXPAND, 5);
|
|
|
|
|
|
|
|
auto* rows = new wxFlexGridSizer(0, 2, 0, 0);
|
|
|
|
rows->AddGrowableCol(1);
|
|
|
|
|
|
|
|
m_text = new wxStaticText(this, wxID_ANY, "Checking for latest version...");
|
|
|
|
rows->Add(m_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
|
|
|
|
|
|
|
{
|
|
|
|
auto* right_side = new wxBoxSizer(wxHORIZONTAL);
|
|
|
|
|
2022-08-31 12:04:09 +02:00
|
|
|
m_updateButton = new wxButton(this, wxID_ANY, _("Update"));
|
|
|
|
m_updateButton->Bind(wxEVT_BUTTON, &CemuUpdateWindow::OnUpdateButton, this);
|
|
|
|
right_side->Add(m_updateButton, 0, wxALL, 5);
|
2022-08-22 22:21:23 +02:00
|
|
|
|
2022-08-31 12:04:09 +02:00
|
|
|
m_cancelButton = new wxButton(this, wxID_ANY, _("Cancel"));
|
|
|
|
m_cancelButton->Bind(wxEVT_BUTTON, &CemuUpdateWindow::OnCancelButton, this);
|
|
|
|
right_side->Add(m_cancelButton, 0, wxALL, 5);
|
2022-08-22 22:21:23 +02:00
|
|
|
|
|
|
|
rows->Add(right_side, 1, wxALIGN_RIGHT, 5);
|
|
|
|
}
|
|
|
|
|
|
|
|
m_changelog = new wxHyperlinkCtrl(this, wxID_ANY, _("Changelog"), wxEmptyString);
|
|
|
|
rows->Add(m_changelog, 0, wxLEFT | wxBOTTOM | wxRIGHT | wxEXPAND, 5);
|
|
|
|
|
|
|
|
sizer->Add(rows, 0, wxALL | wxEXPAND, 5);
|
|
|
|
|
|
|
|
SetSizerAndFit(sizer);
|
|
|
|
Centre(wxBOTH);
|
|
|
|
|
|
|
|
Bind(wxEVT_CLOSE_WINDOW, &CemuUpdateWindow::OnClose, this);
|
|
|
|
Bind(wxEVT_RESULT, &CemuUpdateWindow::OnResult, this);
|
|
|
|
Bind(wxEVT_PROGRESS, &CemuUpdateWindow::OnGaugeUpdate, this);
|
|
|
|
m_thread = std::thread(&CemuUpdateWindow::WorkerThread, this);
|
|
|
|
|
2022-08-31 12:04:09 +02:00
|
|
|
m_updateButton->Hide();
|
2022-08-22 22:21:23 +02:00
|
|
|
m_changelog->Hide();
|
|
|
|
}
|
|
|
|
|
|
|
|
CemuUpdateWindow::~CemuUpdateWindow()
|
|
|
|
{
|
|
|
|
m_order = WorkerOrder::Exit;
|
|
|
|
if (m_thread.joinable())
|
|
|
|
m_thread.join();
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t CemuUpdateWindow::WriteStringCallback(char* ptr, size_t size, size_t nmemb, void* userdata)
|
|
|
|
{
|
|
|
|
((std::string*)userdata)->append(ptr, size * nmemb);
|
|
|
|
return size * nmemb;
|
|
|
|
};
|
|
|
|
|
|
|
|
std::string _curlUrlEscape(CURL* curl, const std::string& input)
|
|
|
|
{
|
|
|
|
char* escapedStr = curl_easy_escape(curl, input.c_str(), input.size());
|
|
|
|
std::string r(escapedStr);
|
|
|
|
curl_free(escapedStr);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2022-08-31 12:04:09 +02:00
|
|
|
std::string _curlUrlUnescape(CURL* curl, std::string_view input)
|
|
|
|
{
|
|
|
|
int decodedLen = 0;
|
|
|
|
const char* decoded = curl_easy_unescape(curl, input.data(), input.size(), &decodedLen);
|
|
|
|
return std::string(decoded, decodedLen);
|
|
|
|
}
|
|
|
|
|
|
|
|
// returns true if update is available and sets output parameters
|
|
|
|
bool CemuUpdateWindow::QueryUpdateInfo(std::string& downloadUrlOut, std::string& changelogUrlOut)
|
2022-08-22 22:21:23 +02:00
|
|
|
{
|
|
|
|
std::string buffer;
|
2022-08-31 12:04:09 +02:00
|
|
|
std::string urlStr("https://cemu.info/api2/version.php?v=");
|
2022-08-22 22:21:23 +02:00
|
|
|
auto* curl = curl_easy_init();
|
2022-08-31 12:04:09 +02:00
|
|
|
urlStr.append(_curlUrlEscape(curl, BUILD_VERSION_STRING));
|
|
|
|
#if BOOST_OS_LINUX
|
|
|
|
urlStr.append("&platform=linux");
|
|
|
|
#elif BOOST_OS_WINDOWS
|
|
|
|
urlStr.append("&platform=windows");
|
|
|
|
#elif BOOST_OS_MACOS
|
|
|
|
urlStr.append("&platform=macos_x86");
|
|
|
|
#elif
|
|
|
|
|
|
|
|
#error Name for current platform is missing
|
|
|
|
#endif
|
2022-08-22 22:21:23 +02:00
|
|
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_URL, urlStr.c_str());
|
2022-08-31 12:04:09 +02:00
|
|
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
|
2022-08-22 22:21:23 +02:00
|
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteStringCallback);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
|
|
|
|
|
|
|
bool result = false;
|
|
|
|
CURLcode cr = curl_easy_perform(curl);
|
|
|
|
if (cr == CURLE_OK)
|
|
|
|
{
|
|
|
|
long http_code = 0;
|
|
|
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
|
|
|
if (http_code != 0 && http_code != 200)
|
|
|
|
{
|
|
|
|
forceLog_printf("Update check failed (http code: %d)", http_code);
|
|
|
|
cemu_assert_debug(false);
|
|
|
|
return false;
|
|
|
|
}
|
2022-08-31 12:04:09 +02:00
|
|
|
|
2022-08-22 22:21:23 +02:00
|
|
|
std::vector<std::string> tokens;
|
|
|
|
const boost::char_separator<char> sep{ "|" };
|
|
|
|
for (const auto& token : boost::tokenizer(buffer, sep))
|
|
|
|
tokens.emplace_back(token);
|
|
|
|
|
2022-08-31 12:04:09 +02:00
|
|
|
if (tokens.size() >= 3 && tokens[0] == "UPDATE")
|
2022-08-22 22:21:23 +02:00
|
|
|
{
|
2022-08-31 12:04:09 +02:00
|
|
|
// first token: "UPDATE"
|
|
|
|
// second token: Download URL
|
|
|
|
// third token: Changelog URL
|
|
|
|
// we allow more tokens in case we ever want to add extra information for future releases
|
|
|
|
downloadUrlOut = _curlUrlUnescape(curl, tokens[1]);
|
|
|
|
changelogUrlOut = _curlUrlUnescape(curl, tokens[2]);
|
|
|
|
if (!downloadUrlOut.empty() && !changelogUrlOut.empty())
|
|
|
|
result = true;
|
2022-08-22 22:21:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
forceLog_printf("Update check failed with CURL error %d", (int)cr);
|
|
|
|
cemu_assert_debug(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
curl_easy_cleanup(curl);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2022-08-31 12:04:09 +02:00
|
|
|
std::future<bool> CemuUpdateWindow::IsUpdateAvailableAsync()
|
2022-08-22 22:21:23 +02:00
|
|
|
{
|
|
|
|
return std::async(std::launch::async, CheckVersion);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CemuUpdateWindow::CheckVersion()
|
|
|
|
{
|
2022-08-31 12:04:09 +02:00
|
|
|
std::string downloadUrl, changelogUrl;
|
|
|
|
return QueryUpdateInfo(downloadUrl, changelogUrl);
|
2022-08-22 22:21:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int CemuUpdateWindow::ProgressCallback(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal,
|
2022-08-31 12:04:09 +02:00
|
|
|
curl_off_t ulnow)
|
2022-08-22 22:21:23 +02:00
|
|
|
{
|
|
|
|
auto* thisptr = (CemuUpdateWindow*)clientp;
|
|
|
|
auto* event = new wxCommandEvent(wxEVT_PROGRESS);
|
|
|
|
event->SetInt((int)dlnow);
|
|
|
|
wxQueueEvent(thisptr, event);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CemuUpdateWindow::DownloadCemuZip(const std::string& url, const fs::path& filename)
|
|
|
|
{
|
|
|
|
FileStream* fsUpdateFile = FileStream::createFile2(filename);
|
|
|
|
if (!fsUpdateFile)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
bool result = false;
|
|
|
|
auto* curl = curl_easy_init();
|
|
|
|
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
|
|
|
curl_easy_setopt(curl, CURLOPT_NOBODY, 1);
|
2022-08-31 12:04:09 +02:00
|
|
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, BUILD_VERSION_WITH_NAME_STRING);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
|
|
|
|
auto r = curl_easy_perform(curl);
|
|
|
|
if (r == CURLE_OK)
|
2022-08-22 22:21:23 +02:00
|
|
|
{
|
|
|
|
long http_code = 0;
|
|
|
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
|
|
|
if (http_code != 0 && http_code != 200)
|
|
|
|
{
|
|
|
|
cemuLog_log(LogType::Force, "Unable to download cemu update zip file from {} (http error: {})", url, http_code);
|
|
|
|
curl_easy_cleanup(curl);
|
|
|
|
return false;
|
|
|
|
}
|
2022-08-31 12:04:09 +02:00
|
|
|
|
2022-08-22 22:21:23 +02:00
|
|
|
curl_off_t update_size;
|
|
|
|
if (curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &update_size) == CURLE_OK)
|
2022-08-31 12:04:09 +02:00
|
|
|
m_gaugeMaxValue = (int)update_size;
|
2022-08-22 22:21:23 +02:00
|
|
|
|
|
|
|
|
|
|
|
auto _curlWriteData = +[](void* ptr, size_t size, size_t nmemb, void* ctx) -> size_t
|
|
|
|
{
|
|
|
|
FileStream* fs = (FileStream*)ctx;
|
|
|
|
const size_t writeSize = size * nmemb;
|
|
|
|
fs->writeData(ptr, writeSize);
|
|
|
|
return writeSize;
|
|
|
|
};
|
|
|
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_NOBODY, 0);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _curlWriteData);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fsUpdateFile);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallback);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, this);
|
|
|
|
|
|
|
|
auto curl_result = std::async(std::launch::async, [](CURL* curl, long* http_code)
|
2022-08-31 12:04:09 +02:00
|
|
|
{
|
|
|
|
const auto r = curl_easy_perform(curl);
|
|
|
|
curl_easy_cleanup(curl);
|
|
|
|
return r;
|
|
|
|
}, curl, &http_code);
|
2022-08-22 22:21:23 +02:00
|
|
|
while (!curl_result.valid())
|
|
|
|
{
|
|
|
|
if (m_order == WorkerOrder::Exit)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
|
|
}
|
2022-08-31 12:04:09 +02:00
|
|
|
|
2022-08-22 22:21:23 +02:00
|
|
|
result = curl_result.get() == CURLE_OK;
|
|
|
|
|
|
|
|
delete fsUpdateFile;
|
|
|
|
}
|
|
|
|
else
|
2022-08-31 12:04:09 +02:00
|
|
|
{
|
|
|
|
cemuLog_log(LogType::Force, "Cemu zip download failed with error {}", r);
|
2022-08-22 22:21:23 +02:00
|
|
|
curl_easy_cleanup(curl);
|
2022-08-31 12:04:09 +02:00
|
|
|
}
|
2022-08-22 22:21:23 +02:00
|
|
|
|
|
|
|
if (!result && fs::exists(filename))
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
fs::remove(filename);
|
|
|
|
}
|
|
|
|
catch (const std::exception& ex)
|
|
|
|
{
|
|
|
|
forceLog_printf("can't remove update.zip on error: %s", ex.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2022-08-31 12:04:09 +02:00
|
|
|
bool CemuUpdateWindow::ExtractUpdate(const fs::path& zipname, const fs::path& targetpath, std::string& cemuFolderName)
|
2022-08-22 22:21:23 +02:00
|
|
|
{
|
2022-08-31 12:04:09 +02:00
|
|
|
cemuFolderName.clear();
|
2022-08-22 22:21:23 +02:00
|
|
|
// open downloaded zip
|
|
|
|
int err;
|
|
|
|
auto* za = zip_open(zipname.string().c_str(), ZIP_RDONLY, &err);
|
|
|
|
if (za == nullptr)
|
|
|
|
{
|
|
|
|
cemuLog_log(LogType::Force, "Cannot open zip file: {}", zipname.string());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto count = zip_get_num_entries(za, 0);
|
2022-08-31 12:04:09 +02:00
|
|
|
m_gaugeMaxValue = count;
|
2022-08-22 22:21:23 +02:00
|
|
|
for (auto i = 0; i < count; i++)
|
|
|
|
{
|
|
|
|
if (m_order == WorkerOrder::Exit)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
zip_stat_t sb{};
|
|
|
|
if (zip_stat_index(za, i, 0, &sb) == 0)
|
|
|
|
{
|
|
|
|
fs::path fname = targetpath;
|
|
|
|
fname /= sb.name;
|
|
|
|
|
|
|
|
const auto len = strlen(sb.name);
|
|
|
|
if (strcmp(sb.name, ".") == 0 || strcmp(sb.name, "..") == 0)
|
|
|
|
{
|
|
|
|
// protection
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (sb.name[len - 1] == '/' || sb.name[len - 1] == '\\')
|
|
|
|
{
|
|
|
|
// directory
|
|
|
|
try
|
|
|
|
{
|
|
|
|
if (!exists(fname))
|
|
|
|
create_directory(fname);
|
|
|
|
}
|
|
|
|
catch (const std::exception& ex)
|
|
|
|
{
|
|
|
|
SystemException sys(ex);
|
|
|
|
forceLog_printf("can't create folder \"%s\" for update: %s", sb.name, sys.what());
|
|
|
|
}
|
2022-08-31 12:04:09 +02:00
|
|
|
// the root should have only one Cemu_... directory, we track it here
|
|
|
|
if ((std::count(sb.name, sb.name + len, '/') + std::count(sb.name, sb.name + len, '\\')) == 1)
|
|
|
|
{
|
|
|
|
if (!cemuFolderName.empty())
|
|
|
|
forceLog_printf("update zip has multiple folders in root");
|
|
|
|
cemuFolderName.assign(sb.name, len - 1);
|
|
|
|
}
|
2022-08-22 22:21:23 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// file
|
|
|
|
auto* zf = zip_fopen_index(za, i, 0);
|
|
|
|
if (!zf)
|
|
|
|
{
|
|
|
|
forceLog_printf("can't open zip file \"%s\"", sb.name);
|
|
|
|
zip_close(za);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<char> buffer(sb.size);
|
|
|
|
const auto read = zip_fread(zf, buffer.data(), sb.size);
|
|
|
|
if (read != (sint64)sb.size)
|
|
|
|
{
|
|
|
|
forceLog_printf("could only read 0x%x of 0x%x bytes from zip file \"%s\"", read, sb.size, sb.name);
|
|
|
|
zip_close(za);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto* file = fopen(fname.string().c_str(), "wb");
|
|
|
|
if (file == nullptr)
|
|
|
|
{
|
|
|
|
forceLog_printf("can't create update file \"%s\"", sb.name);
|
|
|
|
zip_close(za);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
fwrite(buffer.data(), 1, buffer.size(), file);
|
|
|
|
fflush(file);
|
|
|
|
fclose(file);
|
|
|
|
|
|
|
|
zip_fclose(zf);
|
|
|
|
|
|
|
|
if ((i / 10) * 10 == i)
|
|
|
|
{
|
|
|
|
auto* event = new wxCommandEvent(wxEVT_PROGRESS);
|
|
|
|
event->SetInt(i);
|
|
|
|
wxQueueEvent(this, event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto* event = new wxCommandEvent(wxEVT_PROGRESS);
|
2022-08-31 12:04:09 +02:00
|
|
|
event->SetInt(m_gaugeMaxValue);
|
2022-08-22 22:21:23 +02:00
|
|
|
wxQueueEvent(this, event);
|
|
|
|
|
|
|
|
zip_close(za);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CemuUpdateWindow::WorkerThread()
|
|
|
|
{
|
|
|
|
const auto tmppath = fs::temp_directory_path() / L"cemu_update";
|
|
|
|
std::error_code ec;
|
|
|
|
// clean leftovers
|
|
|
|
if (exists(tmppath))
|
|
|
|
remove_all(tmppath, ec);
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
std::unique_lock lock(m_mutex);
|
|
|
|
while (m_order == WorkerOrder::Idle)
|
|
|
|
m_condition.wait_for(lock, std::chrono::milliseconds(125));
|
|
|
|
|
|
|
|
if (m_order == WorkerOrder::Exit)
|
|
|
|
break;
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
if (m_order == WorkerOrder::CheckVersion)
|
|
|
|
{
|
|
|
|
auto* event = new wxCommandEvent(wxEVT_RESULT);
|
2022-08-31 12:04:09 +02:00
|
|
|
if (QueryUpdateInfo(m_downloadUrl, m_changelogUrl))
|
2022-08-22 22:21:23 +02:00
|
|
|
event->SetInt((int)Result::UpdateAvailable);
|
|
|
|
else
|
|
|
|
event->SetInt((int)Result::NoUpdateAvailable);
|
|
|
|
|
|
|
|
wxQueueEvent(this, event);
|
|
|
|
}
|
|
|
|
else if (m_order == WorkerOrder::UpdateVersion)
|
|
|
|
{
|
|
|
|
// download update
|
2022-08-31 12:04:09 +02:00
|
|
|
const std::string url = m_downloadUrl;
|
2022-08-22 22:21:23 +02:00
|
|
|
if (!exists(tmppath))
|
|
|
|
create_directory(tmppath);
|
|
|
|
|
|
|
|
const auto update_file = tmppath / L"update.zip";
|
|
|
|
if (DownloadCemuZip(url, update_file))
|
|
|
|
{
|
|
|
|
auto* event = new wxCommandEvent(wxEVT_RESULT);
|
|
|
|
event->SetInt((int)Result::UpdateDownloaded);
|
|
|
|
wxQueueEvent(this, event);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto* event = new wxCommandEvent(wxEVT_RESULT);
|
|
|
|
event->SetInt((int)Result::UpdateDownloadError);
|
|
|
|
wxQueueEvent(this, event);
|
|
|
|
m_order = WorkerOrder::Idle;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (m_order == WorkerOrder::Exit)
|
|
|
|
break;
|
|
|
|
|
|
|
|
// extract
|
2022-08-31 12:04:09 +02:00
|
|
|
std::string cemuFolderName;
|
|
|
|
if (!ExtractUpdate(update_file, tmppath, cemuFolderName))
|
|
|
|
{
|
|
|
|
cemuLog_log(LogType::Force, "Extracting Cemu zip failed");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (cemuFolderName.empty())
|
|
|
|
{
|
|
|
|
cemuLog_log(LogType::Force, "Cemu folder not found in zip");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto expected_path = tmppath / cemuFolderName;
|
|
|
|
if (exists(expected_path))
|
2022-08-22 22:21:23 +02:00
|
|
|
{
|
|
|
|
auto* event = new wxCommandEvent(wxEVT_RESULT);
|
|
|
|
event->SetInt((int)Result::ExtractSuccess);
|
|
|
|
wxQueueEvent(this, event);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto* event = new wxCommandEvent(wxEVT_RESULT);
|
|
|
|
event->SetInt((int)Result::ExtractError);
|
|
|
|
wxQueueEvent(this, event);
|
|
|
|
|
|
|
|
if (exists(tmppath))
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
fs::remove(tmppath);
|
|
|
|
}
|
|
|
|
catch (const std::exception& ex)
|
|
|
|
{
|
|
|
|
SystemException sys(ex);
|
|
|
|
forceLog_printf("can't remove extracted tmp files: %s", sys.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_order == WorkerOrder::Exit)
|
|
|
|
break;
|
|
|
|
|
|
|
|
// apply update
|
|
|
|
std::wstring target_directory = ActiveSettings::GetPath().generic_wstring();
|
|
|
|
if (target_directory[target_directory.size() - 1] == '/')
|
|
|
|
target_directory = target_directory.substr(0, target_directory.size() - 1); // remove trailing /
|
|
|
|
|
|
|
|
// get exe name
|
|
|
|
const auto exec = ActiveSettings::GetFullPath();
|
|
|
|
const auto target_exe = fs::path(exec).replace_extension("exe.backup");
|
|
|
|
fs::rename(exec, target_exe);
|
2022-08-31 12:04:09 +02:00
|
|
|
m_restartFile = exec;
|
2022-08-22 22:21:23 +02:00
|
|
|
|
|
|
|
const auto index = expected_path.wstring().size();
|
|
|
|
int counter = 0;
|
|
|
|
for (const auto& it : fs::recursive_directory_iterator(expected_path))
|
|
|
|
{
|
|
|
|
const auto filename = it.path().wstring().substr(index);
|
|
|
|
auto target_file = target_directory + filename;
|
|
|
|
try
|
|
|
|
{
|
|
|
|
if (is_directory(it))
|
|
|
|
{
|
|
|
|
if (!fs::exists(target_file))
|
|
|
|
fs::create_directory(target_file);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-08-31 12:04:09 +02:00
|
|
|
if (it.path().filename() == L"Cemu.exe")
|
2022-08-22 22:21:23 +02:00
|
|
|
fs::rename(it.path(), fs::path(target_file).replace_filename(exec.filename()));
|
|
|
|
else
|
|
|
|
fs::rename(it.path(), target_file);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (const std::exception& ex)
|
|
|
|
{
|
|
|
|
SystemException sys(ex);
|
|
|
|
forceLog_printf("applying update error: %s", sys.what());
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((counter++ / 10) * 10 == counter)
|
|
|
|
{
|
|
|
|
auto* event = new wxCommandEvent(wxEVT_PROGRESS);
|
|
|
|
event->SetInt(counter);
|
|
|
|
wxQueueEvent(this, event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto* event = new wxCommandEvent(wxEVT_PROGRESS);
|
2022-08-31 12:04:09 +02:00
|
|
|
event->SetInt(m_gaugeMaxValue);
|
2022-08-22 22:21:23 +02:00
|
|
|
wxQueueEvent(this, event);
|
|
|
|
|
|
|
|
auto* result_event = new wxCommandEvent(wxEVT_RESULT);
|
|
|
|
result_event->SetInt((int)Result::Success);
|
|
|
|
wxQueueEvent(this, result_event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (const std::exception& ex)
|
|
|
|
{
|
|
|
|
SystemException sys(ex);
|
|
|
|
forceLog_printf("update error: %s", sys.what());
|
|
|
|
|
|
|
|
// clean leftovers
|
|
|
|
if (exists(tmppath))
|
|
|
|
remove_all(tmppath, ec);
|
|
|
|
|
|
|
|
auto* result_event = new wxCommandEvent(wxEVT_RESULT);
|
|
|
|
result_event->SetInt((int)Result::Error);
|
|
|
|
wxQueueEvent(this, result_event);
|
|
|
|
}
|
|
|
|
|
|
|
|
m_order = WorkerOrder::Idle;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CemuUpdateWindow::OnClose(wxCloseEvent& event)
|
|
|
|
{
|
|
|
|
event.Skip();
|
2022-08-31 12:04:09 +02:00
|
|
|
|
2022-08-26 04:03:26 +02:00
|
|
|
#if BOOST_OS_WINDOWS
|
2022-08-31 12:04:09 +02:00
|
|
|
if (m_restartRequired && !m_restartFile.empty() && fs::exists(m_restartFile))
|
2022-08-22 22:21:23 +02:00
|
|
|
{
|
|
|
|
PROCESS_INFORMATION pi{};
|
|
|
|
STARTUPINFO si{};
|
|
|
|
si.cb = sizeof(si);
|
|
|
|
|
|
|
|
std::wstring cmdline = GetCommandLineW();
|
|
|
|
const auto index = cmdline.find('"', 1);
|
|
|
|
cemu_assert_debug(index != std::wstring::npos);
|
2022-08-31 12:04:09 +02:00
|
|
|
cmdline = L"\"" + m_restartFile.wstring() + L"\"" + cmdline.substr(index + 1);
|
2022-08-22 22:21:23 +02:00
|
|
|
|
|
|
|
HANDLE lock = CreateMutex(nullptr, TRUE, L"Global\\cemu_update_lock");
|
|
|
|
CreateProcess(nullptr, (wchar_t*)cmdline.c_str(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi);
|
2022-08-31 12:04:09 +02:00
|
|
|
|
2022-08-26 19:41:42 +02:00
|
|
|
exit(0);
|
2022-08-22 22:21:23 +02:00
|
|
|
}
|
|
|
|
#else
|
|
|
|
cemuLog_log(LogType::Force, "unimplemented - restart on update");
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CemuUpdateWindow::OnResult(wxCommandEvent& event)
|
|
|
|
{
|
|
|
|
switch ((Result)event.GetInt())
|
|
|
|
{
|
|
|
|
case Result::NoUpdateAvailable:
|
2022-08-31 12:04:09 +02:00
|
|
|
m_cancelButton->SetLabel(_("Exit"));
|
2022-08-22 22:21:23 +02:00
|
|
|
m_text->SetLabel(_("No update available!"));
|
|
|
|
m_gauge->SetValue(100);
|
|
|
|
break;
|
|
|
|
case Result::UpdateAvailable:
|
2022-08-31 12:04:09 +02:00
|
|
|
{
|
|
|
|
if (!m_changelogUrl.empty())
|
2022-08-22 22:21:23 +02:00
|
|
|
{
|
2022-08-31 12:04:09 +02:00
|
|
|
m_changelog->SetURL(m_changelogUrl);
|
|
|
|
m_changelog->Show();
|
2022-08-22 22:21:23 +02:00
|
|
|
}
|
2022-08-31 12:04:09 +02:00
|
|
|
else
|
|
|
|
m_changelog->Hide();
|
|
|
|
|
|
|
|
m_updateButton->Show();
|
|
|
|
|
|
|
|
m_text->SetLabel(_("Update available!"));
|
|
|
|
m_cancelButton->SetLabel(_("Exit"));
|
|
|
|
break;
|
|
|
|
}
|
2022-08-22 22:21:23 +02:00
|
|
|
case Result::UpdateDownloaded:
|
|
|
|
m_text->SetLabel(_("Extracting update..."));
|
|
|
|
m_gauge->SetValue(0);
|
|
|
|
break;
|
|
|
|
case Result::UpdateDownloadError:
|
2022-08-31 12:04:09 +02:00
|
|
|
m_updateButton->Enable();
|
2022-08-22 22:21:23 +02:00
|
|
|
m_text->SetLabel(_("Couldn't download the update!"));
|
|
|
|
break;
|
|
|
|
case Result::ExtractSuccess:
|
|
|
|
m_text->SetLabel(_("Applying update..."));
|
|
|
|
m_gauge->SetValue(0);
|
2022-08-31 12:04:09 +02:00
|
|
|
m_cancelButton->Disable();
|
2022-08-22 22:21:23 +02:00
|
|
|
break;
|
|
|
|
case Result::ExtractError:
|
2022-08-31 12:04:09 +02:00
|
|
|
m_updateButton->Enable();
|
|
|
|
m_cancelButton->Enable();
|
2022-08-22 22:21:23 +02:00
|
|
|
m_text->SetLabel(_("Extracting failed!"));
|
|
|
|
break;
|
|
|
|
case Result::Success:
|
2022-08-31 12:04:09 +02:00
|
|
|
m_cancelButton->Enable();
|
|
|
|
m_updateButton->Hide();
|
2022-08-22 22:21:23 +02:00
|
|
|
|
|
|
|
m_text->SetLabel(_("Success"));
|
2022-08-31 12:04:09 +02:00
|
|
|
m_cancelButton->SetLabel(_("Restart"));
|
|
|
|
m_restartRequired = true;
|
2022-08-22 22:21:23 +02:00
|
|
|
break;
|
2022-08-31 12:04:09 +02:00
|
|
|
default:;
|
2022-08-22 22:21:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CemuUpdateWindow::OnGaugeUpdate(wxCommandEvent& event)
|
|
|
|
{
|
2022-08-31 12:04:09 +02:00
|
|
|
const int total_size = m_gaugeMaxValue > 0 ? m_gaugeMaxValue : 10000000;
|
2022-08-22 22:21:23 +02:00
|
|
|
m_gauge->SetValue((event.GetInt() * 100) / total_size);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CemuUpdateWindow::OnUpdateButton(const wxCommandEvent& event)
|
|
|
|
{
|
|
|
|
std::unique_lock lock(m_mutex);
|
|
|
|
m_order = WorkerOrder::UpdateVersion;
|
|
|
|
|
|
|
|
m_condition.notify_all();
|
|
|
|
|
2022-08-31 12:04:09 +02:00
|
|
|
m_updateButton->Disable();
|
2022-08-22 22:21:23 +02:00
|
|
|
|
|
|
|
m_text->SetLabel(_("Downloading update..."));
|
|
|
|
}
|
|
|
|
|
|
|
|
void CemuUpdateWindow::OnCancelButton(const wxCommandEvent& event)
|
|
|
|
{
|
|
|
|
Close();
|
|
|
|
}
|