mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-01-14 10:59:11 +01:00
350 lines
9.6 KiB
C++
350 lines
9.6 KiB
C++
#include "gui/wxgui.h"
|
|
#include "gui/GameUpdateWindow.h"
|
|
#include "util/helpers/helpers.h"
|
|
|
|
#include <filesystem>
|
|
#include <sstream>
|
|
#include "util/helpers/SystemException.h"
|
|
#include "gui/CemuApp.h"
|
|
#include "Cafe/TitleList/GameInfo.h"
|
|
#include "gui/helpers/wxHelpers.h"
|
|
#include "wxHelper.h"
|
|
|
|
std::string _GetTitleIdTypeStr(TitleId titleId)
|
|
{
|
|
TitleIdParser tip(titleId);
|
|
switch (tip.GetType())
|
|
{
|
|
case TitleIdParser::TITLE_TYPE::AOC:
|
|
return _("DLC").ToStdString();
|
|
case TitleIdParser::TITLE_TYPE::BASE_TITLE:
|
|
return _("Base game").ToStdString();
|
|
case TitleIdParser::TITLE_TYPE::BASE_TITLE_DEMO:
|
|
return _("Demo").ToStdString();
|
|
case TitleIdParser::TITLE_TYPE::SYSTEM_TITLE:
|
|
case TitleIdParser::TITLE_TYPE::SYSTEM_OVERLAY_TITLE:
|
|
return _("System title").ToStdString();
|
|
case TitleIdParser::TITLE_TYPE::SYSTEM_DATA:
|
|
return _("System data title").ToStdString();
|
|
case TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE:
|
|
return _("Update").ToStdString();
|
|
default:
|
|
break;
|
|
}
|
|
return "Unknown";
|
|
}
|
|
|
|
bool IsRunningInWine();
|
|
|
|
bool GameUpdateWindow::ParseUpdate(const fs::path& metaPath)
|
|
{
|
|
m_title_info = TitleInfo(metaPath);
|
|
if (!m_title_info.IsValid())
|
|
return false;
|
|
fs::path target_location = ActiveSettings::GetMlcPath(m_title_info.GetInstallPath());
|
|
std::error_code ec;
|
|
if (fs::exists(target_location, ec))
|
|
{
|
|
try
|
|
{
|
|
const TitleInfo tmp(target_location);
|
|
if (!tmp.IsValid())
|
|
{
|
|
// does not exist / is not valid. We allow to overwrite it
|
|
}
|
|
else
|
|
{
|
|
TitleIdParser tip(m_title_info.GetAppTitleId());
|
|
TitleIdParser tipOther(tmp.GetAppTitleId());
|
|
|
|
if (tip.GetType() != tipOther.GetType())
|
|
{
|
|
std::string typeStrToInstall = _GetTitleIdTypeStr(m_title_info.GetAppTitleId());
|
|
std::string typeStrCurrentlyInstalled = _GetTitleIdTypeStr(tmp.GetAppTitleId());
|
|
|
|
std::string wxMsg = wxHelper::MakeUTF8(_("It seems that there is already a title installed at the target location but it has a different type.\nCurrently installed: \'{}\' Installing: \'{}\'\n\nThis can happen for titles which were installed with very old Cemu versions.\nDo you still want to continue with the installation? It will replace the currently installed title."));
|
|
wxMessageDialog dialog(this, fmt::format(fmt::runtime(wxMsg), typeStrCurrentlyInstalled, typeStrToInstall), _("Warning"), wxCENTRE | wxYES_NO | wxICON_EXCLAMATION);
|
|
if (dialog.ShowModal() != wxID_YES)
|
|
return false;
|
|
}
|
|
else if (tmp.GetAppTitleVersion() == m_title_info.GetAppTitleVersion())
|
|
{
|
|
wxMessageDialog dialog(this, _("It seems that the selected title is already installed, do you want to reinstall it?"), _("Warning"), wxCENTRE | wxYES_NO);
|
|
if (dialog.ShowModal() != wxID_YES)
|
|
return false;
|
|
}
|
|
else if (tmp.GetAppTitleVersion() > m_title_info.GetAppTitleVersion())
|
|
{
|
|
wxMessageDialog dialog(this, _("It seems that a newer version is already installed, do you still want to install the older version?"), _("Warning"), wxCENTRE | wxYES_NO);
|
|
if (dialog.ShowModal() != wxID_YES)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// temp rename until done
|
|
m_backup_folder = target_location;
|
|
m_backup_folder.replace_extension(".backup");
|
|
|
|
std::error_code ec;
|
|
while (fs::exists(m_backup_folder, ec) || ec)
|
|
{
|
|
fs::remove_all(m_backup_folder, ec);
|
|
|
|
if (ec)
|
|
{
|
|
const auto error_msg = wxStringFormat2(_("Error when trying to move former title installation:\n{}"), GetSystemErrorMessage(ec));
|
|
wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this);
|
|
return false;
|
|
}
|
|
|
|
// wait so filesystem doesnt
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
}
|
|
|
|
fs::rename(target_location, m_backup_folder);
|
|
}
|
|
catch (const std::exception& ex)
|
|
{
|
|
forceLog_printf("GameUpdateWindow::ParseUpdate exist-error: %s at %s", ex.what(), target_location.generic_u8string().c_str());
|
|
}
|
|
}
|
|
|
|
m_target_path = target_location;
|
|
|
|
fs::path source(metaPath);
|
|
|
|
m_source_paths =
|
|
{
|
|
fs::path(source).append("content"),
|
|
fs::path(source).append("code"),
|
|
fs::path(source).append("meta")
|
|
};
|
|
|
|
m_required_size = 0;
|
|
for (auto& path : m_source_paths)
|
|
{
|
|
for (const fs::directory_entry& f : fs::recursive_directory_iterator(path))
|
|
{
|
|
if (is_regular_file(f.path()))
|
|
m_required_size += file_size(f.path());
|
|
}
|
|
}
|
|
|
|
// checking size is buggy on Wine (on Steam Deck this would return values too small to install bigger updates) - we therefore skip this step
|
|
if(!IsRunningInWine())
|
|
{
|
|
const fs::space_info targetSpace = fs::space(ActiveSettings::GetMlcPath());
|
|
if (targetSpace.free <= m_required_size)
|
|
{
|
|
auto string = wxStringFormat(_("Not enough space available.\nRequired: {0} MB\nAvailable: {1} MB"), L"%lld %lld", (m_required_size / 1024 / 1024), (targetSpace.free / 1024 / 1024));
|
|
throw std::runtime_error(string);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
GameUpdateWindow::GameUpdateWindow(wxWindow& parent, const fs::path& filePath)
|
|
: wxDialog(&parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxCAPTION | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX),
|
|
m_thread_state(ThreadRunning)
|
|
{
|
|
try
|
|
{
|
|
#if BOOST_OS_WINDOWS
|
|
SetLastError(0);
|
|
#endif
|
|
if(!ParseUpdate(filePath))
|
|
throw AbortException();
|
|
}
|
|
catch (const std::runtime_error& ex)
|
|
{
|
|
throw SystemException(ex);
|
|
}
|
|
|
|
auto sizer = new wxBoxSizer(wxVERTICAL);
|
|
|
|
TitleIdParser tip(GetTitleId());
|
|
|
|
if (tip.GetType() == TitleIdParser::TITLE_TYPE::AOC)
|
|
SetTitle(_("Installing DLC..."));
|
|
else if (tip.GetType() == TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE)
|
|
SetTitle(_("Installing update..."));
|
|
else if (tip.IsSystemTitle())
|
|
SetTitle(_("Installing system title..."));
|
|
else
|
|
SetTitle(_("Installing title..."));
|
|
|
|
m_processBar = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxSize(500, 20), wxGA_HORIZONTAL);
|
|
m_processBar->SetValue(0);
|
|
m_processBar->SetRange((sint32)(m_required_size / 1000));
|
|
sizer->Add(m_processBar, 0, wxALL | wxEXPAND, 5);
|
|
|
|
wxButton* m_cancelButton = new wxButton(this, wxID_ANY, _("Cancel"), wxDefaultPosition, wxDefaultSize, 0);
|
|
m_cancelButton->Bind(wxEVT_BUTTON, &GameUpdateWindow::OnCancelButton, this);
|
|
sizer->Add(m_cancelButton, 0, wxALIGN_RIGHT | wxALL, 5);
|
|
|
|
this->SetSizer(sizer);
|
|
this->Centre(wxBOTH);
|
|
|
|
wxWindowBase::Layout();
|
|
wxWindowBase::Fit();
|
|
|
|
m_timer = new wxTimer(this);
|
|
this->Bind(wxEVT_TIMER, &GameUpdateWindow::OnUpdate, this);
|
|
this->Bind(wxEVT_CLOSE_WINDOW, &GameUpdateWindow::OnClose, this);
|
|
m_timer->Start(250);
|
|
|
|
m_thread_state = ThreadRunning;
|
|
m_thread = std::thread(&GameUpdateWindow::ThreadWork, this);
|
|
}
|
|
|
|
void GameUpdateWindow::ThreadWork()
|
|
{
|
|
fs::directory_entry currentDirEntry;
|
|
try
|
|
{
|
|
// create base directories
|
|
for (auto& path : m_source_paths)
|
|
{
|
|
if (!path.has_stem())
|
|
continue;
|
|
|
|
fs::path targetDir = fs::path(m_target_path) / path.stem();
|
|
create_directories(targetDir);
|
|
}
|
|
|
|
for (auto& path : m_source_paths)
|
|
{
|
|
if (m_thread_state == ThreadCanceled)
|
|
break;
|
|
|
|
if (!path.has_parent_path())
|
|
continue;
|
|
|
|
const auto len = path.parent_path().string().size() + 1;
|
|
for (const fs::directory_entry& f : fs::recursive_directory_iterator {path})
|
|
{
|
|
if (m_thread_state == ThreadCanceled)
|
|
break;
|
|
|
|
currentDirEntry = f;
|
|
fs::path relative(f.path().string().substr(len));
|
|
fs::path target = fs::path(m_target_path) / relative;
|
|
if (is_directory(f))
|
|
{
|
|
create_directories(target);
|
|
continue;
|
|
}
|
|
|
|
copy(f, target, fs::copy_options::overwrite_existing);
|
|
if (is_regular_file(f.path()))
|
|
{
|
|
m_processed_size += file_size(f.path());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (const std::exception& ex)
|
|
{
|
|
std::stringstream error_msg;
|
|
error_msg << GetSystemErrorMessage(ex);
|
|
|
|
if(currentDirEntry != fs::directory_entry{})
|
|
error_msg << fmt::format("\n{}\n{}",_("Current file:").ToStdString(), _pathToUtf8(currentDirEntry.path()));
|
|
|
|
m_thread_exception = error_msg.str();
|
|
m_thread_state = ThreadCanceled;
|
|
}
|
|
|
|
if (m_thread_state == ThreadCanceled)
|
|
{
|
|
if(fs::exists(m_target_path))
|
|
fs::remove_all(m_target_path);
|
|
}
|
|
else
|
|
m_thread_state = ThreadFinished;
|
|
}
|
|
|
|
GameUpdateWindow::~GameUpdateWindow()
|
|
{
|
|
m_timer->Stop();
|
|
if (m_thread.joinable())
|
|
m_thread.join();
|
|
}
|
|
|
|
int GameUpdateWindow::ShowModal()
|
|
{
|
|
wxDialog::ShowModal();
|
|
return m_thread_state == ThreadCanceled ? wxID_CANCEL : wxID_OK;
|
|
}
|
|
|
|
void GameUpdateWindow::OnClose(wxCloseEvent& event)
|
|
{
|
|
if (m_thread_state == ThreadRunning)
|
|
{
|
|
wxMessageDialog dialog(this, _("Do you really want to cancel the installation process?\n\nCanceling the process will delete the applied files."), _("Info"), wxCENTRE | wxYES_NO);
|
|
if (dialog.ShowModal() != wxID_YES)
|
|
return;
|
|
|
|
m_thread_state = ThreadCanceled;
|
|
}
|
|
|
|
m_timer->Stop();
|
|
if (m_thread.joinable())
|
|
m_thread.join();
|
|
|
|
if(!m_backup_folder.empty())
|
|
{
|
|
if(m_thread_state == ThreadCanceled)
|
|
{
|
|
// restore backup
|
|
try
|
|
{
|
|
if(fs::exists(m_target_path))
|
|
fs::remove_all(m_target_path);
|
|
|
|
fs::rename(m_backup_folder, m_target_path);
|
|
}
|
|
catch (const std::exception& ex)
|
|
{
|
|
forceLogDebug_printf("can't restore update backup: %s",ex.what());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// delete backup
|
|
try
|
|
{
|
|
if(fs::exists(m_backup_folder))
|
|
fs::remove_all(m_backup_folder);
|
|
}
|
|
catch (const std::exception& ex)
|
|
{
|
|
forceLogDebug_printf("can't delete update backup: %s",ex.what());
|
|
}
|
|
}
|
|
|
|
m_backup_folder.clear();
|
|
}
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
void GameUpdateWindow::OnUpdate(const wxTimerEvent& event)
|
|
{
|
|
if (m_thread_state != ThreadRunning)
|
|
{
|
|
Close();
|
|
return;
|
|
}
|
|
|
|
const auto processedSize = (sint32)(m_processed_size / 1000);
|
|
if (m_processBar->GetValue() != processedSize)
|
|
m_processBar->SetValue(processedSize);
|
|
}
|
|
|
|
void GameUpdateWindow::OnCancelButton(const wxCommandEvent& event)
|
|
{
|
|
Close();
|
|
}
|