Cemu/src/gui/TitleManager.cpp

887 lines
30 KiB
C++
Raw Normal View History

2022-08-22 22:21:23 +02:00
#include "gui/TitleManager.h"
#include "gui/helpers/wxCustomEvents.h"
#include "gui/helpers/wxCustomData.h"
#include "Cafe/TitleList/GameInfo.h"
#include "util/helpers/helpers.h"
#include "gui/helpers/wxHelpers.h"
#include "gui/wxHelper.h"
2022-08-22 22:21:23 +02:00
#include "gui/components/wxTitleManagerList.h"
#include "gui/components/wxDownloadManagerList.h"
#include "gui/GameUpdateWindow.h"
#include "gui/dialogs/SaveImport/SaveTransfer.h"
#include <wx/listbase.h>
#include <wx/sizer.h>
#include <wx/listctrl.h>
#include <wx/statline.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/statbmp.h>
#include <wx/panel.h>
#include <wx/bmpbuttn.h>
#include <wx/choice.h>
#include <wx/msgdlg.h>
#include <wx/filedlg.h>
#include <wx/combobox.h>
#include <wx/checkbox.h>
#include <pugixml.hpp>
#include <zip.h>
#include <wx/dirdlg.h>
#include <wx/notebook.h>
#include "config/ActiveSettings.h"
#include "gui/dialogs/SaveImport/SaveImportWindow.h"
#include "Cafe/Account/Account.h"
#include "Cemu/Tools/DownloadManager/DownloadManager.h"
#include "gui/CemuApp.h"
#include "Cafe/TitleList/TitleList.h"
2022-08-26 04:03:26 +02:00
#include "resource/embedded/resources.h"
2022-08-22 22:21:23 +02:00
#include "Cafe/TitleList/SaveList.h"
wxDEFINE_EVENT(wxEVT_TITLE_FOUND, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_TITLE_SEARCH_COMPLETE, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_DL_TITLE_UPDATE, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_DL_DISCONNECT_COMPLETE, wxCommandEvent);
wxPanel* TitleManager::CreateTitleManagerPage()
{
auto* panel = new wxPanel(m_notebook);
auto* sizer = new wxBoxSizer(wxVERTICAL);
{
auto* row = new wxFlexGridSizer(0, 4, 0, 0);
row->AddGrowableCol(1);
row->Add(new wxStaticText(panel, wxID_ANY, _("Filter")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
m_filter = new wxTextCtrl(panel, wxID_ANY);
m_filter->Bind(wxEVT_TEXT, &TitleManager::OnFilterChanged, this);
row->Add(m_filter, 1, wxALL | wxEXPAND, 5);
const wxImage refresh = wxBITMAP_PNG_FROM_DATA(PNG_REFRESH).ConvertToImage();
2022-08-22 22:21:23 +02:00
m_refresh_button = new wxBitmapButton(panel, wxID_ANY, refresh.Scale(16, 16));
m_refresh_button->Disable();
m_refresh_button->Bind(wxEVT_BUTTON, &TitleManager::OnRefreshButton, this);
m_refresh_button->SetToolTip(_("Refresh"));
row->Add(m_refresh_button, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
auto* help_button = new wxStaticBitmap(panel, wxID_ANY, wxBITMAP_PNG_FROM_DATA(PNG_HELP));
2022-08-22 22:21:23 +02:00
help_button->SetToolTip(wxStringFormat2(_("The following prefixes are supported:\n{0}\n{1}\n{2}\n{3}\n{4}"),
"titleid:", "name:", "type:", "version:", "region:"));
row->Add(help_button, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
sizer->Add(row, 0, wxEXPAND, 5);
}
sizer->Add(new wxStaticLine(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL), 0, wxEXPAND | wxALL, 5);
m_title_list = new wxTitleManagerList(panel);
m_title_list->SetSizeHints(800, 600);
m_title_list->Bind(wxEVT_LIST_ITEM_SELECTED, &TitleManager::OnTitleSelected, this);
sizer->Add(m_title_list, 1, wxALL | wxEXPAND, 5);
sizer->Add(new wxStaticLine(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL), 0, wxEXPAND | wxALL, 5);
{
auto* row = new wxFlexGridSizer(0, 3, 0, 0);
row->AddGrowableCol(2);
auto* install_button = new wxButton(panel, wxID_ANY, _("Install title"));
install_button->Bind(wxEVT_BUTTON, &TitleManager::OnInstallTitle, this);
row->Add(install_button, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
row->Add(new wxStaticLine(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL), 0, wxEXPAND | wxALL, 5);
{
m_save_panel = new wxPanel(panel);
auto* save_sizer = new wxFlexGridSizer(0, 7, 0, 0);
save_sizer->Add(new wxStaticText(m_save_panel, wxID_ANY, _("Account")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
m_save_account_list = new wxChoice(m_save_panel, wxID_ANY);
m_save_account_list->SetMinSize({ 170, -1 });
m_save_account_list->Bind(wxEVT_CHOICE, &TitleManager::OnSaveAccountSelected, this);
save_sizer->Add(m_save_account_list, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
auto* save_open = new wxButton(m_save_panel, wxID_ANY, _("Open directory"));
save_open->Bind(wxEVT_BUTTON, &TitleManager::OnSaveOpenDirectory, this);
save_open->SetToolTip(_("Open the directory of the save entry"));
save_open->Disable();
save_sizer->Add(save_open, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
auto* save_transfer = new wxButton(m_save_panel, wxID_ANY, _("Transfer"));
save_transfer->Bind(wxEVT_BUTTON, &TitleManager::OnSaveTransfer, this);
save_transfer->SetToolTip(_("Transfers the save entry to another persistent account id"));
save_transfer->Disable();
save_sizer->Add(save_transfer, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
auto* save_delete = new wxButton(m_save_panel, wxID_ANY, _("Delete"));
save_delete->Bind(wxEVT_BUTTON, &TitleManager::OnSaveDelete, this);
save_delete->SetToolTip(_("Permanently delete the save entry"));
2022-08-22 22:21:23 +02:00
save_delete->Disable();
save_sizer->Add(save_delete, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
m_save_import = new wxButton(m_save_panel, wxID_ANY, _("Import"));
m_save_import->Bind(wxEVT_BUTTON, &TitleManager::OnSaveImport, this);
m_save_import->SetToolTip(_("Imports a zipped save entry"));
save_sizer->Add(m_save_import, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxTOP | wxBOTTOM, 5);
auto* export_bttn = new wxButton(m_save_panel, wxID_ANY, _("Export"));
export_bttn->Bind(wxEVT_BUTTON, &TitleManager::OnSaveExport, this);
export_bttn->SetToolTip(_("Exports the selected save entry as zip file"));
export_bttn->Disable();
save_sizer->Add(export_bttn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT | wxTOP | wxBOTTOM, 5);
m_save_panel->SetSizerAndFit(save_sizer);
row->Add(m_save_panel, 1, wxRESERVE_SPACE_EVEN_IF_HIDDEN | wxALIGN_CENTER_VERTICAL, 0);
m_save_panel->Hide(); // hide by default
}
sizer->Add(row, 0, wxEXPAND, 5);
}
panel->SetSizerAndFit(sizer);
return panel;
}
wxPanel* TitleManager::CreateDownloadManagerPage()
{
auto* panel = new wxPanel(m_notebook);
auto* sizer = new wxBoxSizer(wxVERTICAL);
{
auto* row = new wxBoxSizer(wxHORIZONTAL);
m_account = new wxChoice(panel, wxID_ANY);
m_account->SetMinSize({ 250,-1 });
auto accounts = Account::GetAccounts();
if (!accounts.empty())
{
const auto id = GetConfig().account.m_persistent_id.GetValue();
for (const auto& a : accounts)
{
m_account->Append(a.ToString(), (void*)static_cast<uintptr_t>(a.GetPersistentId()));
if(a.GetPersistentId() == id)
{
m_account->SetSelection(m_account->GetCount() - 1);
}
}
}
row->Add(m_account, 0, wxALL, 5);
m_connect = new wxButton(panel, wxID_ANY, _("Connect"));
m_connect->Bind(wxEVT_BUTTON, &TitleManager::OnConnect, this);
row->Add(m_connect, 0, wxALL, 5);
sizer->Add(row, 0, wxEXPAND, 5);
}
m_status_text = new wxStaticText(panel, wxID_ANY, _("Select an account and press Connect"));
this->Bind(wxEVT_SET_TEXT, &TitleManager::OnSetStatusText, this);
sizer->Add(m_status_text, 0, wxALL, 5);
sizer->Add(new wxStaticLine(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL), 0, wxEXPAND | wxALL, 5);
{
auto* row = new wxFlexGridSizer(0, 3, 0, 0);
m_show_titles = new wxCheckBox(panel, wxID_ANY, _("Show available titles"));
m_show_titles->SetValue(true);
m_show_titles->Enable(false);
row->Add(m_show_titles, 0, wxALL, 5);
m_show_titles->Bind(wxEVT_CHECKBOX, &TitleManager::OnDlFilterCheckbox, this);
m_show_updates = new wxCheckBox(panel, wxID_ANY, _("Show available updates"));
m_show_updates->SetValue(true);
m_show_updates->Enable(false);
row->Add(m_show_updates, 0, wxALL, 5);
m_show_updates->Bind(wxEVT_CHECKBOX, &TitleManager::OnDlFilterCheckbox, this);
m_show_installed = new wxCheckBox(panel, wxID_ANY, _("Show installed"));
m_show_installed->SetValue(true);
m_show_installed->Enable(false);
row->Add(m_show_installed, 0, wxALL, 5);
m_show_installed->Bind(wxEVT_CHECKBOX, &TitleManager::OnDlFilterCheckbox, this);
sizer->Add(row, 0, wxEXPAND, 5);
}
m_download_list = new wxDownloadManagerList(panel);
m_download_list->SetSizeHints(800, 600);
m_download_list->Bind(wxEVT_LIST_ITEM_SELECTED, &TitleManager::OnTitleSelected, this);
sizer->Add(m_download_list, 1, wxALL | wxEXPAND, 5);
panel->SetSizerAndFit(sizer);
return panel;
}
TitleManager::TitleManager(wxWindow* parent, TitleManagerPage default_page)
2022-10-07 14:30:06 +02:00
: wxFrame(parent, wxID_ANY, _("Title Manager"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE | wxTAB_TRAVERSAL)
2022-08-22 22:21:23 +02:00
{
SetIcon(wxICON(X_BOX));
auto* sizer = new wxBoxSizer(wxVERTICAL);
m_notebook = new wxNotebook(this, wxID_ANY);
m_notebook->AddPage(CreateTitleManagerPage(), _("Title Manager"), default_page == TitleManagerPage::TitleManager);
m_notebook->AddPage(CreateDownloadManagerPage(), _("Download Manager"), default_page == TitleManagerPage::DownloadManager);
sizer->Add(m_notebook, 1, wxEXPAND);
m_status_bar = CreateStatusBar(2, wxSTB_SIZEGRIP);
m_status_bar->SetStatusText(_("Searching for titles..."));
this->SetSizerAndFit(sizer);
this->Centre(wxBOTH);
this->Bind(wxEVT_SET_STATUS_BAR_TEXT, &TitleManager::OnSetStatusBarText, this);
this->Bind(wxEVT_TITLE_FOUND, &TitleManager::OnTitleFound, this);
this->Bind(wxEVT_TITLE_SEARCH_COMPLETE, &TitleManager::OnTitleSearchComplete, this);
this->Bind(wxEVT_DL_TITLE_UPDATE, &TitleManager::OnDownloadableTitleUpdate, this);
this->Bind(wxEVT_DL_DISCONNECT_COMPLETE, &TitleManager::OnDisconnect, this);
// TODO typing on title list should change filter text and filter!
// if download manager is already active then restore state
DownloadManager* dlMgr = DownloadManager::GetInstance(false);
if (dlMgr && dlMgr->IsConnected())
{
dlMgr->setUserData(this);
dlMgr->registerCallbacks(
TitleManager::Callback_ConnectStatusUpdate,
TitleManager::Callback_AddDownloadableTitle,
TitleManager::Callback_RemoveDownloadableTitle);
SetConnected(true);
}
m_callbackId = CafeTitleList::RegisterCallback([](CafeTitleListCallbackEvent* evt, void* ctx) { ((TitleManager*)ctx)->HandleTitleListCallback(evt); }, this);
}
TitleManager::~TitleManager()
{
CafeTitleList::UnregisterCallback(m_callbackId);
// unregister callbacks for download manager
DownloadManager* dlMgr = DownloadManager::GetInstance(false);
if (dlMgr)
{
dlMgr->setUserData(nullptr);
dlMgr->registerCallbacks(
nullptr,
nullptr,
nullptr);
// if download manager is still downloading / installing then show a warning
if (dlMgr->hasActiveDownloads())
{
static bool s_showedBGDownloadWarning = false;
if (!s_showedBGDownloadWarning)
{
wxMessageBox(_("Currently active downloads will continue in the background."), _("Information"), wxOK | wxCENTRE, this);
s_showedBGDownloadWarning = true;
}
}
}
m_running = false;
}
void TitleManager::SetFocusAndTab(TitleManagerPage page)
{
m_notebook->SetSelection((int)page);
this->SetFocus();
}
void TitleManager::SetDownloadStatusText(const wxString& text)
{
auto* evt = new wxCommandEvent(wxEVT_SET_TEXT);
evt->SetEventObject(m_status_text);
evt->SetString(text);
wxQueueEvent(this, evt);
}
void TitleManager::HandleTitleListCallback(CafeTitleListCallbackEvent* evt)
{
if (evt->eventType == CafeTitleListCallbackEvent::TYPE::SCAN_FINISHED)
{
auto* evt = new wxCommandEvent(wxEVT_TITLE_SEARCH_COMPLETE);
wxQueueEvent(this, evt);
}
}
void TitleManager::OnTitleFound(wxCommandEvent& event)
{
auto* obj = dynamic_cast<wxTitleManagerList::TitleEntryData_t*>(event.GetClientObject());
wxASSERT(obj);
m_title_list->AddTitle(obj);
}
void TitleManager::OnTitleSearchComplete(wxCommandEvent& event)
{
m_isScanning = false;
if (m_connectRequested)
{
InitiateConnect();
m_connectRequested = false;
}
// update status bar text
m_title_list->SortEntries(-1);
m_status_bar->SetStatusText(wxStringFormat2(_("Found {0} games, {1} updates, {2} DLCs and {3} save entries"),
2022-08-22 22:21:23 +02:00
m_title_list->GetCountByType(wxTitleManagerList::EntryType::Base) + m_title_list->GetCountByType(wxTitleManagerList::EntryType::System),
m_title_list->GetCountByType(wxTitleManagerList::EntryType::Update),
m_title_list->GetCountByType(wxTitleManagerList::EntryType::Dlc),
m_title_list->GetCountByType(wxTitleManagerList::EntryType::Save)
));
// re-filter if any filter is set
const auto filter = m_filter->GetValue();
if (!filter.IsEmpty())
m_title_list->Filter(m_filter->GetValue());
m_title_list->AutosizeColumns();
m_refresh_button->Enable();
}
void TitleManager::OnSetStatusBarText(wxSetStatusBarTextEvent& event)
{
m_status_bar->SetStatusText(_(event.GetText()), event.GetNumber());
}
void TitleManager::OnFilterChanged(wxCommandEvent& event)
{
event.Skip();
m_title_list->Filter(m_filter->GetValue());
}
void TitleManager::OnRefreshButton(wxCommandEvent& event)
{
m_refresh_button->Disable();
m_isScanning = true;
// m_title_list->ClearItems(); -> Dont clear. Refresh() triggers incremental updates via notifications
m_status_bar->SetStatusText(_("Searching for titles..."));
CafeTitleList::Refresh();
}
void TitleManager::OnInstallTitle(wxCommandEvent& event)
{
wxFileDialog openFileDialog(this, _("Select title to install"), "", "", "meta.xml|meta.xml", wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (openFileDialog.ShowModal() == wxID_CANCEL)
return;
fs::path filePath(openFileDialog.GetPath().wc_str());
try
{
filePath = filePath.parent_path();
filePath = filePath.parent_path();
GameUpdateWindow frame(*this, filePath);
const int updateResult = frame.ShowModal();
if (updateResult == wxID_OK)
{
CafeTitleList::AddTitleFromPath(frame.GetTargetPath());
}
else
{
if (frame.GetExceptionMessage().empty())
wxMessageBox(_("Update installation has been canceled!"));
else
{
throw std::runtime_error(frame.GetExceptionMessage());
}
}
}
catch (const AbortException&)
{
// ignored
}
catch (const std::exception& ex)
{
wxMessageBox(ex.what(), _("Update error"));
}
}
static void PopulateSavePersistentIds(wxTitleManagerList::TitleEntry& entry)
{
if (!entry.persistent_ids.empty())
return;
cemu_assert(entry.type == wxTitleManagerList::EntryType::Save);
SaveInfo saveInfo = CafeSaveList::GetSaveByTitleId(entry.title_id);
if (!saveInfo.IsValid())
return;
fs::path savePath = saveInfo.GetPath();
savePath /= "user";
std::error_code ec;
for (auto it : fs::directory_iterator(savePath, ec))
{
if(!it.is_directory(ec))
continue;
if(fs::is_empty(it.path()))
continue;
2022-08-22 22:21:23 +02:00
std::string dirName = it.path().filename().string();
if(!std::regex_match(dirName, std::regex("[0-9a-fA-F]{8}")))
continue;
entry.persistent_ids.emplace_back(ConvertString<uint32>(dirName, 16));
2022-08-22 22:21:23 +02:00
}
}
void TitleManager::OnTitleSelected(wxListEvent& event)
{
event.Skip();
const auto entry = m_title_list->GetSelectedTitleEntry();
if(entry.has_value() && entry->type == wxTitleManagerList::EntryType::Save)
{
m_save_panel->Show();
m_save_account_list->Clear();
PopulateSavePersistentIds(*entry);
// an account must be selected before any of the control buttons can be used
for(auto&& v : m_save_panel->GetChildren())
{
if (dynamic_cast<wxButton*>(v) && v->GetId() != m_save_import->GetId()) // import is always enabled
v->Disable();
}
const auto& accounts = Account::GetAccounts();
for (const auto& id : entry->persistent_ids)
{
const auto it = std::find_if(accounts.cbegin(), accounts.cend(), [id](const auto& acc) { return acc.GetPersistentId() == id; });
if(it != accounts.cend())
{
m_save_account_list->Append(fmt::format("{:x} ({})", id,
boost::nowide::narrow(it->GetMiiName().data(), it->GetMiiName().size())),
(void*)(uintptr_t)id);
}
else
m_save_account_list->Append(fmt::format("{:x}", id), (void*)(uintptr_t)id);
}
}
else
{
m_save_panel->Hide();
}
}
void TitleManager::OnSaveOpenDirectory(wxCommandEvent& event)
{
const auto selection= m_save_account_list->GetSelection();
if (selection == wxNOT_FOUND)
return;
const auto entry = m_title_list->GetSelectedTitleEntry();
if (!entry.has_value())
return;
const auto persistent_id = (uint32)(uintptr_t)m_save_account_list->GetClientData(selection);
const fs::path target = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/{:08x}", (uint32)(entry->title_id >> 32), (uint32)(entry->title_id & 0xFFFFFFFF), persistent_id);
if (!fs::exists(target) || !fs::is_directory(target))
return;
wxLaunchDefaultBrowser(wxHelper::FromUtf8(fmt::format("file:{}", _pathToUtf8(target))));
2022-08-22 22:21:23 +02:00
}
void TitleManager::OnSaveDelete(wxCommandEvent& event)
{
const auto selection_index = m_save_account_list->GetSelection();
if (selection_index == wxNOT_FOUND)
return;
const auto selection = m_save_account_list->GetStringSelection();
if (selection.IsEmpty())
return;
const auto msg = wxStringFormat2(_("Are you really sure that you want to delete the save entry for {}"), selection);
const auto result = wxMessageBox(msg, _("Warning"), wxYES_NO | wxCENTRE | wxICON_EXCLAMATION, this);
if (result == wxNO)
return;
auto entry = m_title_list->GetSelectedTitleEntry();
if (!entry.has_value())
return;
const auto persistent_id = (uint32)(uintptr_t)m_save_account_list->GetClientData(selection_index);
const fs::path target = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/{:08x}", (uint32)(entry->title_id >> 32), (uint32)(entry->title_id & 0xFFFFFFFF), persistent_id);
if (!fs::exists(target) || !fs::is_directory(target))
return;
// edit meta saveinfo.xml
bool meta_file_edited = false;
const fs::path saveinfo = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/saveinfo.xml", (uint32)(entry->title_id >> 32), (uint32)(entry->title_id & 0xFFFFFFFF));
if (fs::exists(saveinfo) || fs::is_regular_file(saveinfo))
{
pugi::xml_document doc;
if (doc.load_file(saveinfo.c_str()))
{
auto info_node = doc.child("info");
if(info_node)
{
auto persistend_id_string = fmt::format(L"{:08x}", persistent_id);
const auto delete_entry = info_node.find_child([&persistend_id_string](const pugi::xml_node& node)
{
return boost::iequals(node.attribute("persistentId").as_string(), persistend_id_string);
});
if (delete_entry)
{
info_node.remove_child(delete_entry);
meta_file_edited = doc.save_file(saveinfo.c_str());
}
}
}
}
if (!meta_file_edited)
cemuLog_log(LogType::Force, "TitleManager::OnSaveDelete: couldn't delete save entry in saveinfo.xml: {}", _pathToUtf8(saveinfo));
2022-08-22 22:21:23 +02:00
// remove from title entry
auto& persistent_ids = entry->persistent_ids;
persistent_ids.erase(std::remove(persistent_ids.begin(), persistent_ids.end(), persistent_id), persistent_ids.end());
std::error_code ec;
fs::remove_all(target, ec);
if (ec)
{
const auto error_msg = wxStringFormat2(_("Error when trying to delete the save directory:\n{}"), GetSystemErrorMessage(ec));
wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this);
return;
}
m_save_account_list->Delete(selection_index);
}
void TitleManager::OnSaveTransfer(wxCommandEvent& event)
{
const auto selection_index = m_save_account_list->GetSelection();
if (selection_index == wxNOT_FOUND)
return;
const auto selection = m_save_account_list->GetStringSelection();
if (selection.IsEmpty())
return;
auto entry = m_title_list->GetSelectedTitleEntry();
if (!entry.has_value())
return;
const auto persistent_id = (uint32)(uintptr_t)m_save_account_list->GetClientData(selection_index);
SaveTransfer transfer(this, entry->title_id, selection, persistent_id);
if (transfer.ShowModal() == wxCANCEL)
return;
// remove old id entry
auto& persistent_ids = entry->persistent_ids;
persistent_ids.erase(std::remove(persistent_ids.begin(), persistent_ids.end(), persistent_id), persistent_ids.end());
// add new id if not added yet
const auto new_id = transfer.GetTargetPersistentId();
if (new_id != 0 && std::find(persistent_ids.cbegin(), persistent_ids.cend(), new_id) == persistent_ids.cend())
{
persistent_ids.emplace_back(new_id);
const auto& account = Account::GetAccount(new_id);
if(account.GetPersistentId() == new_id)
m_save_account_list->Append(fmt::format("{:x} ({})", new_id,
boost::nowide::narrow(account.GetMiiName().data(), account.GetMiiName().size())),
(void*)(uintptr_t)new_id);
else
m_save_account_list->Append(fmt::format("{:x}", new_id), (void*)(uintptr_t)new_id);
}
m_save_account_list->Delete(selection_index);
}
void TitleManager::OnSaveAccountSelected(wxCommandEvent& event)
{
event.Skip();
for (auto&& v : m_save_panel->GetChildren())
{
if (!v->IsEnabled())
v->Enable();
}
}
void TitleManager::OnSaveExport(wxCommandEvent& event)
{
const auto selection_index = m_save_account_list->GetSelection();
if (selection_index == wxNOT_FOUND)
return;
const auto selection = m_save_account_list->GetStringSelection();
if (selection.IsEmpty())
return;
const auto entry = m_title_list->GetSelectedTitleEntry();
if (!entry.has_value())
return;
const auto persistent_id = (uint32)(uintptr_t)m_save_account_list->GetClientData(selection_index);
wxFileDialog path_dialog(this, _("Select a target file to export the save entry"), entry->path.string(), wxEmptyString, "Exported save entry (*.zip)|*.zip", wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if (path_dialog.ShowModal() != wxID_OK)
return;
const auto path = path_dialog.GetPath();
if (path.empty())
return;
int ze;
auto* zip = zip_open(path.ToUTF8().data(), ZIP_CREATE | ZIP_TRUNCATE, &ze);
if (!zip)
{
zip_error_t ziperror;
zip_error_init_with_code(&ziperror, ze);
const auto error_msg = wxStringFormat2(_("Error when creating the zip for the save entry:\n{}"), zip_error_strerror(&ziperror));
wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
return;
}
// grab all files to zip
const auto savedir = fs::path(entry->path).append(fmt::format("user/{:08x}", persistent_id));
const auto savedir_str = savedir.generic_u8string();
for(const auto& it : fs::recursive_directory_iterator(savedir))
{
if (it.path() == "." || it.path() == "..")
continue;
const auto entryname = it.path().generic_u8string();
if(fs::is_directory(it.path()))
{
if(zip_dir_add(zip, (const char*)entryname.substr(savedir_str.size() + 1).c_str(), ZIP_FL_ENC_UTF_8) < 0 )
{
const auto error_msg = wxStringFormat2(_("Error when trying to add a directory to the zip:\n{}"), zip_strerror(zip));
wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
}
}
else
{
auto* source = zip_source_file(zip, (const char*)entryname.c_str(), 0, 0);
if(!source)
{
const auto error_msg = wxStringFormat2(_("Error when trying to add a file to the zip:\n{}"), zip_strerror(zip));
wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
}
if (zip_file_add(zip, (const char*)entryname.substr(savedir_str.size() + 1).c_str(), source, ZIP_FL_ENC_UTF_8) < 0)
{
const auto error_msg = wxStringFormat2(_("Error when trying to add a file to the zip:\n{}"), zip_strerror(zip));
wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
zip_source_free(source);
}
}
}
// add own metainfo like file and store title id for verification later
std::string metacontent = fmt::format("titleId = {:#016x}", entry->title_id);
auto* metabuff = zip_source_buffer(zip, metacontent.data(), metacontent.size(), 0);
if(zip_file_add(zip, "cemu_meta", metabuff, ZIP_FL_ENC_UTF_8) < 0)
{
const auto error_msg = wxStringFormat2(_("Error when trying to add cemu_meta file to the zip:\n{}"), zip_strerror(zip));
2022-08-22 22:21:23 +02:00
wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
zip_source_free(metabuff);
}
zip_close(zip);
}
void TitleManager::OnSaveImport(wxCommandEvent& event)
{
auto entry = m_title_list->GetSelectedTitleEntry();
if (!entry.has_value())
return;
SaveImportWindow save_import(this, entry->title_id);
if (save_import.ShowModal() == wxCANCEL)
return;
// add new id if not added yet
auto& persistent_ids = entry->persistent_ids;
const auto new_id = save_import.GetTargetPersistentId();
if (new_id != 0 && std::find(persistent_ids.cbegin(), persistent_ids.cend(), new_id) == persistent_ids.cend())
{
persistent_ids.emplace_back(new_id);
const auto& account = Account::GetAccount(new_id);
if (account.GetPersistentId() == new_id)
m_save_account_list->Append(fmt::format("{:x} ({})", new_id,
boost::nowide::narrow(account.GetMiiName().data(), account.GetMiiName().size())),
(void*)(uintptr_t)new_id);
else
m_save_account_list->Append(fmt::format("{:x}", new_id), (void*)(uintptr_t)new_id);
}
}
void TitleManager::InitiateConnect()
{
// init connection to download manager if queued
uint32 persistentId = (uint32)(uintptr_t)m_account->GetClientData(m_account->GetSelection());
auto& account = Account::GetAccount(persistentId);
DownloadManager* dlMgr = DownloadManager::GetInstance();
dlMgr->reset();
m_download_list->SetCurrentDownloadMgr(dlMgr);
std::string deviceCertBase64 = NCrypto::CertECC::GetDeviceCertificate().encodeToBase64();
if (!NCrypto::SEEPROM_IsPresent())
{
SetDownloadStatusText("Dumped online files not found");
return;
}
SetDownloadStatusText("Connecting...");
// begin async connect
dlMgr->setUserData(this);
dlMgr->registerCallbacks(
TitleManager::Callback_ConnectStatusUpdate,
TitleManager::Callback_AddDownloadableTitle,
TitleManager::Callback_RemoveDownloadableTitle);
dlMgr->connect(account.GetAccountId(), account.GetAccountPasswordCache(), NCrypto::SEEPROM_GetRegion(), NCrypto::GetCountryAsString(account.GetCountry()), NCrypto::GetDeviceId(), NCrypto::GetSerial(), deviceCertBase64);
}
void TitleManager::OnConnect(wxCommandEvent& event)
{
if (!m_isScanning)
{
InitiateConnect();
SetConnected(true);
}
else
{
SetDownloadStatusText(_("Getting installed title information..."));
SetConnected(true);
m_connectRequested = true;
}
}
void TitleManager::OnDlFilterCheckbox(wxCommandEvent& event)
{
m_download_list->Filter2(m_show_titles->GetValue(), m_show_updates->GetValue(), m_show_installed->GetValue());
m_download_list->SortEntries();
}
void TitleManager::OnSetStatusText(wxCommandEvent& event)
{
auto* text = wxDynamicCast(event.GetEventObject(), wxControl);
wxASSERT(text);
text->SetLabel(event.GetString());
}
void TitleManager::OnDownloadableTitleUpdate(wxCommandEvent& event)
{
auto* obj = dynamic_cast<wxDownloadManagerList::TitleEntryData_t*>(event.GetClientObject());
auto entry = obj->GetData();
m_download_list->AddOrUpdateTitle(obj);
}
void TitleManager::OnDisconnect(wxCommandEvent& event)
{
SetConnected(false);
}
void TitleManager::SetConnected(bool state)
{
m_account->Enable(!state);
m_connect->Enable(!state);
m_show_titles->Enable(state);
m_show_updates->Enable(state);
m_show_installed->Enable(state);
m_download_list->Enable(state);
}
void TitleManager::Callback_ConnectStatusUpdate(std::string statusText, DLMGR_STATUS_CODE statusCode)
{
TitleManager* titleManager = static_cast<TitleManager*>(DownloadManager::GetInstance()->getUserData());
titleManager->SetDownloadStatusText(statusText);
if (statusCode == DLMGR_STATUS_CODE::FAILED)
{
auto* evt = new wxCommandEvent(wxEVT_DL_DISCONNECT_COMPLETE);
wxQueueEvent(titleManager, evt); // this will set SetConnected() to false
return;
}
}
void TitleManager::Callback_AddDownloadableTitle(const DlMgrTitleReport& titleInfo)
{
TitleManager* titleManager = static_cast<TitleManager*>(DownloadManager::GetInstance()->getUserData());
wxDownloadManagerList::EntryType type = wxDownloadManagerList::EntryType::Base;
if (((titleInfo.titleId >> 32) & 0xF) == 0xE)
type = wxDownloadManagerList::EntryType::Update;
else if (((titleInfo.titleId >> 32) & 0xF) == 0xC)
type = wxDownloadManagerList::EntryType::DLC;
if (titleInfo.status == DlMgrTitleReport::STATUS::INSTALLABLE || titleInfo.status == DlMgrTitleReport::STATUS::INSTALLABLE_UNFINISHED || titleInfo.status == DlMgrTitleReport::STATUS::INSTALLABLE_UPDATE)
{
// installable title
wxDownloadManagerList::TitleEntry titleEntry(type, false, titleInfo.titleId, titleInfo.version, titleInfo.isPaused);
titleEntry.name = wxString::FromUTF8(titleInfo.name);
titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Available;
titleEntry.progress = 0;
if (titleInfo.status == DlMgrTitleReport::STATUS::INSTALLABLE_UNFINISHED)
titleEntry.progress = 1;
if (titleInfo.status == DlMgrTitleReport::STATUS::INSTALLABLE_UPDATE)
titleEntry.progress = 2;
auto* evt = new wxCommandEvent(wxEVT_DL_TITLE_UPDATE);
evt->SetClientObject(new wxCustomData(titleEntry));
wxQueueEvent(titleManager, evt);
}
else
{
// package
wxDownloadManagerList::TitleEntry titleEntry(type, true, titleInfo.titleId, titleInfo.version, titleInfo.isPaused);
titleEntry.name = wxString::FromUTF8(titleInfo.name);
titleEntry.progress = titleInfo.progress;
titleEntry.progressMax = titleInfo.progressMax;
switch (titleInfo.status)
{
case DlMgrTitleReport::STATUS::INITIALIZING:
titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Initializing;
break;
case DlMgrTitleReport::STATUS::QUEUED:
titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Queued;
break;
case DlMgrTitleReport::STATUS::CHECKING:
titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Checking;
break;
case DlMgrTitleReport::STATUS::DOWNLOADING:
titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Downloading;
titleEntry.progress = titleInfo.progress;
break;
case DlMgrTitleReport::STATUS::VERIFYING:
titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Verifying;
break;
case DlMgrTitleReport::STATUS::INSTALLING:
titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Installing;
break;
case DlMgrTitleReport::STATUS::INSTALLED:
titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Installed;
break;
case DlMgrTitleReport::STATUS::HAS_ERROR:
titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Error;
titleEntry.errorMsg = titleInfo.errorMsg;
break;
default:
titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::None;
break;
}
auto* evt = new wxCommandEvent(wxEVT_DL_TITLE_UPDATE);
evt->SetClientObject(new wxCustomData(titleEntry));
wxQueueEvent(titleManager, evt);
}
}
void TitleManager::Callback_RemoveDownloadableTitle(uint64 titleId, uint16 version)
{
TitleManager* titleManager = static_cast<TitleManager*>(DownloadManager::GetInstance()->getUserData());
assert_dbg();
}