mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-12-30 10:37:07 +00:00
Add all the files
This commit is contained in:
parent
e3db07a16a
commit
d60742f52b
1445 changed files with 430238 additions and 0 deletions
29
src/gui/CMakeLists.txt
Normal file
29
src/gui/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
project(CemuGui)
|
||||
|
||||
include_directories(".")
|
||||
|
||||
file(GLOB_RECURSE CPP_FILES *.cpp)
|
||||
file(GLOB_RECURSE H_FILES *.h)
|
||||
add_library(CemuGui ${CPP_FILES} ${H_FILES})
|
||||
|
||||
set_property(TARGET CemuGui PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||
|
||||
target_sources(CemuGui PRIVATE
|
||||
wxcomponents/checkedlistctrl.cpp
|
||||
wxcomponents/checkedlistctrl.h
|
||||
wxcomponents/checktree.cpp
|
||||
wxcomponents/checktree.h
|
||||
)
|
||||
|
||||
target_precompile_headers(CemuGui PRIVATE ../Common/precompiled.h)
|
||||
|
||||
target_include_directories(CemuGui PRIVATE ../)
|
||||
|
||||
target_link_libraries(CemuGui ${wxWidgets_LIBRARIES})
|
||||
target_link_libraries(CemuGui libzip::zip)
|
||||
target_link_libraries(CemuGui CemuCafe CemuComponents CemuResource)
|
||||
target_link_libraries(CemuGui zarchive)
|
||||
|
||||
if(ENABLE_CUBEB)
|
||||
target_link_libraries(CemuGui cubeb)
|
||||
endif()
|
||||
420
src/gui/CemuApp.cpp
Normal file
420
src/gui/CemuApp.cpp
Normal file
|
|
@ -0,0 +1,420 @@
|
|||
#include "gui/CemuApp.h"
|
||||
#include "gui/MainWindow.h"
|
||||
#include "gui/wxgui.h"
|
||||
#include "config/CemuConfig.h"
|
||||
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h"
|
||||
#include "gui/guiWrapper.h"
|
||||
#include "config/ActiveSettings.h"
|
||||
#include "gui/GettingStartedDialog.h"
|
||||
#include "config/PermanentConfig.h"
|
||||
#include "config/PermanentStorage.h"
|
||||
#include "input/InputManager.h"
|
||||
#include "gui/helpers/wxHelpers.h"
|
||||
#include "Cemu/ncrypto/ncrypto.h"
|
||||
|
||||
#include <wx/image.h>
|
||||
#include <wx/filename.h>
|
||||
|
||||
#include "Cafe/TitleList/TitleList.h"
|
||||
#include "Cafe/TitleList/SaveList.h"
|
||||
|
||||
wxIMPLEMENT_APP_NO_MAIN(CemuApp);
|
||||
|
||||
// defined in guiWrapper.cpp
|
||||
extern WindowInfo g_window_info;
|
||||
extern std::shared_mutex g_mutex;
|
||||
|
||||
// Translation strings to extract for gettext:
|
||||
void unused_translation_dummy()
|
||||
{
|
||||
void(_("Browse"));
|
||||
void(_("Select a file"));
|
||||
void(_("Select a directory"));
|
||||
|
||||
void(_("base"));
|
||||
void(_("update"));
|
||||
void(_("dlc"));
|
||||
void(_("save"));
|
||||
|
||||
void(_("Japan"));
|
||||
void(_("USA"));
|
||||
void(_("Europe"));
|
||||
void(_("Australia"));
|
||||
void(_("China"));
|
||||
void(_("Korea"));
|
||||
void(_("Taiwan"));
|
||||
void(_("Auto"));
|
||||
void(_("many"));
|
||||
|
||||
void(_("Japanese"));
|
||||
void(_("English"));
|
||||
void(_("French"));
|
||||
void(_("German"));
|
||||
void(_("Italian"));
|
||||
void(_("Spanish"));
|
||||
void(_("Chinese"));
|
||||
void(_("Korean"));
|
||||
void(_("Dutch"));
|
||||
void(_("Portugese"));
|
||||
void(_("Russian"));
|
||||
void(_("Taiwanese"));
|
||||
void(_("unknown"));
|
||||
|
||||
|
||||
// account.h
|
||||
void(_("AccountId missing (The account is not connected to a NNID)"));
|
||||
void(_("IsPasswordCacheEnabled is set to false (The remember password option on your Wii U must be enabled for this account before dumping it)"));
|
||||
void(_("AccountPasswordCache is empty (The remember password option on your Wii U must be enabled for this account before dumping it)"));
|
||||
void(_("PrincipalId missing"));
|
||||
}
|
||||
|
||||
|
||||
#pragma optimize( "", off )
|
||||
DLLEXPORT _declspec(noinline) wxTopLevelWindow* wxMainWindowCreated(wxTopLevelWindow* wndPtr, uint32 magicConstant, CemuApp* appPointer)
|
||||
{
|
||||
return wndPtr;
|
||||
}
|
||||
#pragma optimize( "", on )
|
||||
|
||||
bool CemuApp::OnInit()
|
||||
{
|
||||
wxInitAllImageHandlers();
|
||||
|
||||
g_config.Load();
|
||||
m_languages = GetAvailableLanguages();
|
||||
|
||||
const sint32 language = GetConfig().language;
|
||||
if (language != wxLANGUAGE_ENGLISH)
|
||||
{
|
||||
const auto it = std::find_if(m_languages.begin(), m_languages.end(), [language](const wxLanguageInfo* info) { return info->Language == language; });
|
||||
if (it != m_languages.end() && wxLocale::IsAvailable(language))
|
||||
{
|
||||
if (m_locale.Init(language))
|
||||
{
|
||||
m_locale.AddCatalogLookupPathPrefix("./resources");
|
||||
m_locale.AddCatalog("cemu");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_locale.IsOk())
|
||||
{
|
||||
m_locale.Init(wxLANGUAGE_DEFAULT);
|
||||
}
|
||||
|
||||
// fill colour db
|
||||
wxTheColourDatabase->AddColour("ERROR", wxColour(0xCC, 0, 0));
|
||||
wxTheColourDatabase->AddColour("SUCCESS", wxColour(0, 0xbb, 0));
|
||||
|
||||
#if BOOST_OS_WINDOWS
|
||||
const auto parent_path = GetParentProcess();
|
||||
if(parent_path.has_filename())
|
||||
{
|
||||
const auto filename = parent_path.filename().generic_string();
|
||||
if (boost::icontains(filename, "WiiU_USB_Helper"))
|
||||
__fastfail(0);
|
||||
}
|
||||
#endif
|
||||
|
||||
// init input
|
||||
InputManager::instance().load();
|
||||
|
||||
InitializeGlobalVulkan();
|
||||
|
||||
Bind(wxEVT_ACTIVATE_APP, &CemuApp::ActivateApp, this);
|
||||
|
||||
if (!TestWriteAccess(ActiveSettings::GetPath()))
|
||||
wxMessageBox(_("Cemu can't write to its directory.\nPlease move it to a different location or run Cemu as administrator!"), _("Warning"), wxOK | wxCENTRE | wxICON_EXCLAMATION, nullptr);
|
||||
|
||||
auto& config = GetConfig();
|
||||
const bool first_start = !config.did_show_graphic_pack_download;
|
||||
|
||||
CreateDefaultFiles(first_start);
|
||||
|
||||
m_mainFrame = new MainWindow();
|
||||
|
||||
if (first_start)
|
||||
m_mainFrame->ShowGettingStartedDialog();
|
||||
|
||||
std::unique_lock lock(g_mutex);
|
||||
g_window_info.app_active = true;
|
||||
|
||||
SetTopWindow(m_mainFrame);
|
||||
|
||||
// Cemuhook callback
|
||||
wxMainWindowCreated(m_mainFrame, 0xDABABE, this);
|
||||
m_mainFrame->Show();
|
||||
return true;
|
||||
}
|
||||
|
||||
int CemuApp::OnExit()
|
||||
{
|
||||
wxApp::OnExit();
|
||||
#if BOOST_OS_WINDOWS > 0
|
||||
ExitProcess(0);
|
||||
#else
|
||||
exit(0);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if BOOST_OS_WINDOWS
|
||||
void DumpThreadStackTrace();
|
||||
#endif
|
||||
|
||||
void CemuApp::OnAssertFailure(const wxChar* file, int line, const wxChar* func, const wxChar* cond, const wxChar* msg)
|
||||
{
|
||||
cemuLog_createLogFile(false);
|
||||
cemuLog_log(LogType::Force, "Encountered wxWidgets assert!");
|
||||
cemuLog_log(LogType::Force, fmt::format(L"File: {0} Line: {1}", std::wstring_view(file), line));
|
||||
cemuLog_log(LogType::Force, fmt::format(L"Func: {0} Cond: {1}", func, std::wstring_view(cond)));
|
||||
cemuLog_log(LogType::Force, fmt::format(L"Message: {}", std::wstring_view(msg)));
|
||||
|
||||
#if BOOST_OS_WINDOWS
|
||||
DumpThreadStackTrace();
|
||||
#endif
|
||||
cemu_assert_debug(false);
|
||||
}
|
||||
|
||||
int CemuApp::FilterEvent(wxEvent& event)
|
||||
{
|
||||
if(event.GetEventType() == wxEVT_KEY_DOWN)
|
||||
{
|
||||
const auto& key_event = (wxKeyEvent&)event;
|
||||
wxGetKeyState(wxKeyCode::WXK_F17);
|
||||
g_window_info.keydown[fix_raw_keycode(key_event.GetRawKeyCode(), key_event.GetRawKeyFlags())] = true;
|
||||
}
|
||||
else if(event.GetEventType() == wxEVT_KEY_UP)
|
||||
{
|
||||
const auto& key_event = (wxKeyEvent&)event;
|
||||
g_window_info.keydown[fix_raw_keycode(key_event.GetRawKeyCode(), key_event.GetRawKeyFlags())] = false;
|
||||
}
|
||||
|
||||
return wxApp::FilterEvent(event);
|
||||
}
|
||||
|
||||
std::vector<const wxLanguageInfo*> CemuApp::GetAvailableLanguages()
|
||||
{
|
||||
const auto path = ActiveSettings::GetPath("resources");
|
||||
if (!exists(path))
|
||||
return {};
|
||||
|
||||
std::vector<const wxLanguageInfo*> result;
|
||||
for (const auto& p : fs::directory_iterator(path))
|
||||
{
|
||||
if (!fs::is_directory(p))
|
||||
continue;
|
||||
|
||||
const auto& path = p.path();
|
||||
auto filename = path.filename();
|
||||
|
||||
const auto* lang_info = wxLocale::FindLanguageInfo(filename.c_str());
|
||||
if (!lang_info)
|
||||
continue;
|
||||
|
||||
const auto language_file = path / "cemu.mo";
|
||||
if (!fs::exists(language_file))
|
||||
continue;
|
||||
|
||||
result.emplace_back(lang_info);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void CemuApp::CreateDefaultFiles(bool first_start)
|
||||
{
|
||||
std::wstring mlc = GetMLCPath().ToStdWstring();
|
||||
|
||||
// check for mlc01 folder missing if custom path has been set
|
||||
if (!fs::exists(mlc) && !first_start)
|
||||
{
|
||||
const std::wstring message = fmt::format(_(L"Your mlc01 folder seems to be missing.\n\nThis is where Cemu stores save files, game updates and other Wii U files.\n\nThe expected path is:\n{}\n\nDo you want to create the folder at the expected path?").ToStdWstring(), mlc);
|
||||
|
||||
wxMessageDialog dialog(nullptr, message, "Error", wxCENTRE | wxYES_NO | wxCANCEL| wxICON_WARNING);
|
||||
dialog.SetYesNoCancelLabels(_("Yes"), _("No"), _("Select a custom path"));
|
||||
const auto dialogResult = dialog.ShowModal();
|
||||
if (dialogResult == wxID_NO)
|
||||
exit(0);
|
||||
else if(dialogResult == wxID_CANCEL)
|
||||
{
|
||||
if (!SelectMLCPath())
|
||||
return;
|
||||
|
||||
mlc = GetMLCPath();
|
||||
}
|
||||
else
|
||||
{
|
||||
GetConfig().mlc_path = L"";
|
||||
g_config.Save();
|
||||
}
|
||||
}
|
||||
|
||||
// create sys/usr folder in mlc01
|
||||
try
|
||||
{
|
||||
const auto sysFolder = fs::path(mlc).append(L"sys");
|
||||
fs::create_directories(sysFolder);
|
||||
|
||||
const auto usrFolder = fs::path(mlc).append(L"usr");
|
||||
fs::create_directories(usrFolder);
|
||||
fs::create_directories(fs::path(usrFolder).append("title/00050000")); // base
|
||||
fs::create_directories(fs::path(usrFolder).append("title/0005000c")); // dlc
|
||||
fs::create_directories(fs::path(usrFolder).append("title/0005000e")); // update
|
||||
|
||||
// Mii Maker save folders {0x500101004A000, 0x500101004A100, 0x500101004A200},
|
||||
fs::create_directories(fs::path(mlc).append(L"usr/save/00050010/1004a000/user/common/db"));
|
||||
fs::create_directories(fs::path(mlc).append(L"usr/save/00050010/1004a100/user/common/db"));
|
||||
fs::create_directories(fs::path(mlc).append(L"usr/save/00050010/1004a200/user/common/db"));
|
||||
|
||||
// lang files
|
||||
auto langDir = fs::path(mlc).append(L"sys/title/0005001b/1005c000/content");
|
||||
fs::create_directories(langDir);
|
||||
|
||||
auto langFile = fs::path(langDir).append("language.txt");
|
||||
if (!fs::exists(langFile))
|
||||
{
|
||||
std::ofstream file(langFile);
|
||||
if (file.is_open())
|
||||
{
|
||||
const char* langStrings[] = { "ja","en","fr","de","it","es","zh","ko","nl","pt","ru","zh" };
|
||||
for (const char* lang : langStrings)
|
||||
file << fmt::format(R"("{}",)", lang) << std::endl;
|
||||
|
||||
file.flush();
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
auto countryFile = fs::path(langDir).append("country.txt");
|
||||
if (!fs::exists(countryFile))
|
||||
{
|
||||
std::ofstream file(countryFile);
|
||||
for (sint32 i = 0; i < 201; i++)
|
||||
{
|
||||
const char* countryCode = NCrypto::GetCountryAsString(i);
|
||||
if (boost::iequals(countryCode, "NN"))
|
||||
file << "NULL," << std::endl;
|
||||
else
|
||||
file << fmt::format(R"("{}",)", countryCode) << std::endl;
|
||||
}
|
||||
file.flush();
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
std::stringstream errorMsg;
|
||||
errorMsg << fmt::format(_("Couldn't create a required mlc01 subfolder or file!\n\nError: {0}\nTarget path:\n{1}").ToStdString(), ex.what(), boost::nowide::narrow(mlc));
|
||||
|
||||
#if BOOST_OS_WINDOWS > 0
|
||||
const DWORD lastError = GetLastError();
|
||||
if (lastError != ERROR_SUCCESS)
|
||||
errorMsg << fmt::format("\n\n{}", GetSystemErrorMessage(lastError));
|
||||
|
||||
wxMessageBox(errorMsg.str(), "Error", wxOK | wxCENTRE | wxICON_ERROR);
|
||||
#endif
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// cemu directories
|
||||
try
|
||||
{
|
||||
const auto controllerProfileFolder = GetCemuPath(L"controllerProfiles").ToStdWstring();
|
||||
if (!fs::exists(controllerProfileFolder))
|
||||
fs::create_directories(controllerProfileFolder);
|
||||
|
||||
const auto memorySearcherFolder = GetCemuPath(L"memorySearcher").ToStdWstring();
|
||||
if (!fs::exists(memorySearcherFolder))
|
||||
fs::create_directories(memorySearcherFolder);
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
std::stringstream errorMsg;
|
||||
errorMsg << fmt::format(_("Couldn't create a required cemu directory or file!\n\nError: {0}").ToStdString(), ex.what());
|
||||
|
||||
#if BOOST_OS_WINDOWS > 0
|
||||
const DWORD lastError = GetLastError();
|
||||
if (lastError != ERROR_SUCCESS)
|
||||
errorMsg << fmt::format("\n\n{}", GetSystemErrorMessage(lastError));
|
||||
|
||||
|
||||
wxMessageBox(errorMsg.str(), "Error", wxOK | wxCENTRE | wxICON_ERROR);
|
||||
#endif
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool CemuApp::SelectMLCPath(wxWindow* parent)
|
||||
{
|
||||
auto& config = GetConfig();
|
||||
|
||||
std::wstring default_path;
|
||||
if (fs::exists(config.mlc_path.GetValue()))
|
||||
default_path = config.mlc_path.GetValue();
|
||||
|
||||
// try until users selects a valid path or aborts
|
||||
while(true)
|
||||
{
|
||||
wxDirDialog path_dialog(parent, _("Select a mlc directory"), default_path, wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST);
|
||||
if (path_dialog.ShowModal() != wxID_OK || path_dialog.GetPath().empty())
|
||||
return false;
|
||||
|
||||
const auto path = path_dialog.GetPath().ToStdWstring();
|
||||
|
||||
if (!TestWriteAccess(fs::path{ path }))
|
||||
{
|
||||
const auto result = wxMessageBox(_("Cemu can't write to the selected mlc path!\nDo you want to select another path?"), _("Error"), wxYES_NO | wxCENTRE | wxICON_ERROR);
|
||||
if (result == wxYES)
|
||||
continue;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
config.SetMLCPath(path);
|
||||
// update TitleList and SaveList scanner with new MLC path
|
||||
CafeTitleList::SetMLCPath(path);
|
||||
CafeTitleList::Refresh();
|
||||
CafeSaveList::SetMLCPath(path);
|
||||
CafeSaveList::Refresh();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
wxString CemuApp::GetCemuPath()
|
||||
{
|
||||
return ActiveSettings::GetPath().generic_wstring();
|
||||
}
|
||||
|
||||
wxString CemuApp::GetCemuPath(const wxString& cat)
|
||||
{
|
||||
return ActiveSettings::GetPath(cat.ToStdString()).generic_wstring();
|
||||
}
|
||||
|
||||
wxString CemuApp::GetMLCPath()
|
||||
{
|
||||
return ActiveSettings::GetMlcPath().generic_wstring();
|
||||
}
|
||||
|
||||
wxString CemuApp::GetMLCPath(const wxString& cat)
|
||||
{
|
||||
return ActiveSettings::GetMlcPath(cat.ToStdString()).generic_wstring();
|
||||
}
|
||||
|
||||
void CemuApp::ActivateApp(wxActivateEvent& event)
|
||||
{
|
||||
g_window_info.app_active = event.GetActive();
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
{
|
||||
CemuApp& wxGetAppWrapper()
|
||||
{
|
||||
return *static_cast<CemuApp*>(wxApp::GetInstance());
|
||||
};
|
||||
}
|
||||
|
||||
35
src/gui/CemuApp.h
Normal file
35
src/gui/CemuApp.h
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include <wx/app.h>
|
||||
|
||||
class MainWindow;
|
||||
|
||||
class CemuApp : public wxApp
|
||||
{
|
||||
public:
|
||||
bool OnInit() override;
|
||||
int OnExit() override;
|
||||
|
||||
void OnAssertFailure(const wxChar* file, int line, const wxChar* func, const wxChar* cond, const wxChar* msg) override;
|
||||
int FilterEvent(wxEvent& event) override;
|
||||
|
||||
const std::vector<const wxLanguageInfo*>& GetLanguages() const { return m_languages; }
|
||||
static std::vector<const wxLanguageInfo*> GetAvailableLanguages();
|
||||
|
||||
static void CreateDefaultFiles(bool first_start = false);
|
||||
static bool SelectMLCPath(wxWindow* parent = nullptr);
|
||||
|
||||
static wxString GetCemuPath();
|
||||
static wxString GetCemuPath(const wxString& cat);
|
||||
static wxString GetMLCPath();
|
||||
static wxString GetMLCPath(const wxString& cat);
|
||||
private:
|
||||
void ActivateApp(wxActivateEvent& event);
|
||||
|
||||
MainWindow* m_mainFrame = nullptr;
|
||||
|
||||
wxLocale m_locale;
|
||||
std::vector<const wxLanguageInfo*> m_languages;
|
||||
};
|
||||
|
||||
wxDECLARE_APP(CemuApp);
|
||||
621
src/gui/CemuUpdateWindow.cpp
Normal file
621
src/gui/CemuUpdateWindow.cpp
Normal file
|
|
@ -0,0 +1,621 @@
|
|||
#include "gui/CemuUpdateWindow.h"
|
||||
|
||||
#include "Common/version.h"
|
||||
#include "util/helpers/helpers.h"
|
||||
#include "util/helpers/SystemException.h"
|
||||
#include "config/ActiveSettings.h"
|
||||
#include "Common/filestream.h"
|
||||
|
||||
#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,
|
||||
wxCAPTION | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX)
|
||||
{
|
||||
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);
|
||||
|
||||
m_update_button = new wxButton(this, wxID_ANY, _("Update"));
|
||||
m_update_button->Bind(wxEVT_BUTTON, &CemuUpdateWindow::OnUpdateButton, this);
|
||||
right_side->Add(m_update_button, 0, wxALL, 5);
|
||||
|
||||
m_cancel_button = new wxButton(this, wxID_ANY, _("Cancel"));
|
||||
m_cancel_button->Bind(wxEVT_BUTTON, &CemuUpdateWindow::OnCancelButton, this);
|
||||
right_side->Add(m_cancel_button, 0, wxALL, 5);
|
||||
|
||||
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);
|
||||
|
||||
m_update_button->Hide();
|
||||
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;
|
||||
}
|
||||
|
||||
bool CemuUpdateWindow::GetServerVersion(uint64& version, std::string& filename, std::string& changelog_filename)
|
||||
{
|
||||
std::string buffer;
|
||||
std::string urlStr("https://cemu.info/api/cemu_version3.php?version2=");
|
||||
auto* curl = curl_easy_init();
|
||||
urlStr.append(_curlUrlEscape(curl, fmt::format("{}.{}.{}{}", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_SUFFIX)));
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, urlStr.c_str());
|
||||
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;
|
||||
}
|
||||
|
||||
std::vector<std::string> tokens;
|
||||
const boost::char_separator<char> sep{ "|" };
|
||||
for (const auto& token : boost::tokenizer(buffer, sep))
|
||||
{
|
||||
tokens.emplace_back(token);
|
||||
}
|
||||
|
||||
if (tokens.size() >= 2)
|
||||
{
|
||||
const auto latest_version = ConvertString<uint64>(tokens[0]);
|
||||
result = latest_version > 0 && !tokens[1].empty();
|
||||
if (result)
|
||||
{
|
||||
version = latest_version;
|
||||
filename = tokens[1];
|
||||
|
||||
if(tokens.size() >= 3)
|
||||
changelog_filename = tokens[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
forceLog_printf("Update check failed with CURL error %d", (int)cr);
|
||||
cemu_assert_debug(false);
|
||||
}
|
||||
|
||||
curl_easy_cleanup(curl);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::future<bool> CemuUpdateWindow::IsUpdateAvailable()
|
||||
{
|
||||
return std::async(std::launch::async, CheckVersion);
|
||||
}
|
||||
|
||||
bool CemuUpdateWindow::CheckVersion()
|
||||
{
|
||||
uint64 latest_version;
|
||||
std::string filename, changelog;
|
||||
if (!GetServerVersion(latest_version, filename, changelog))
|
||||
return false;
|
||||
|
||||
return IsUpdateAvailable(latest_version);
|
||||
}
|
||||
|
||||
bool CemuUpdateWindow::IsUpdateAvailable(uint64 latest_version)
|
||||
{
|
||||
uint64 version = EMULATOR_SERVER_VERSION;
|
||||
|
||||
return latest_version > version;
|
||||
}
|
||||
|
||||
int CemuUpdateWindow::ProgressCallback(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal,
|
||||
curl_off_t ulnow)
|
||||
{
|
||||
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);
|
||||
if (curl_easy_perform(curl) == CURLE_OK)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
curl_off_t update_size;
|
||||
if (curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &update_size) == CURLE_OK)
|
||||
m_gauge_max_value = (int)update_size;
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
const auto r = curl_easy_perform(curl);
|
||||
curl_easy_cleanup(curl);
|
||||
return r;
|
||||
}, curl, &http_code);
|
||||
while (!curl_result.valid())
|
||||
{
|
||||
if (m_order == WorkerOrder::Exit)
|
||||
return false;
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
result = curl_result.get() == CURLE_OK;
|
||||
|
||||
delete fsUpdateFile;
|
||||
}
|
||||
else
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool CemuUpdateWindow::ExtractUpdate(const fs::path& zipname, const fs::path& targetpath)
|
||||
{
|
||||
// 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);
|
||||
m_gauge_max_value = count;
|
||||
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());
|
||||
}
|
||||
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);
|
||||
event->SetInt(m_gauge_max_value);
|
||||
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);
|
||||
if (GetServerVersion(m_version, m_filename, m_changelog_filename) && IsUpdateAvailable(m_version))
|
||||
event->SetInt((int)Result::UpdateAvailable);
|
||||
else
|
||||
event->SetInt((int)Result::NoUpdateAvailable);
|
||||
|
||||
wxQueueEvent(this, event);
|
||||
}
|
||||
else if (m_order == WorkerOrder::UpdateVersion)
|
||||
{
|
||||
// download update
|
||||
const std::string url = fmt::format("http://cemu.info/releases/{}", m_filename);
|
||||
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
|
||||
const auto expected_path = (tmppath / m_filename).replace_extension("");
|
||||
if (ExtractUpdate(update_file, tmppath) && exists(expected_path))
|
||||
{
|
||||
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);
|
||||
m_restart_file = exec;
|
||||
|
||||
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
|
||||
{
|
||||
if(it.path().filename() == L"Cemu.exe")
|
||||
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);
|
||||
event->SetInt(m_gauge_max_value);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsCemuhookLoaded();
|
||||
void CemuUpdateWindow::OnClose(wxCloseEvent& event)
|
||||
{
|
||||
event.Skip();
|
||||
|
||||
#if BOOST_OS_WINDOWS > 0
|
||||
if (m_restart_required && !m_restart_file.empty() && fs::exists(m_restart_file))
|
||||
{
|
||||
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);
|
||||
cmdline = L"\"" + m_restart_file.wstring() + L"\"" + cmdline.substr(index + 1);
|
||||
|
||||
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);
|
||||
|
||||
if (IsCemuhookLoaded())
|
||||
TerminateProcess(GetCurrentProcess(), 0);
|
||||
else
|
||||
exit(0);
|
||||
}
|
||||
#else
|
||||
cemuLog_log(LogType::Force, "unimplemented - restart on update");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void CemuUpdateWindow::OnResult(wxCommandEvent& event)
|
||||
{
|
||||
switch ((Result)event.GetInt())
|
||||
{
|
||||
case Result::NoUpdateAvailable:
|
||||
m_cancel_button->SetLabel(_("Exit"));
|
||||
m_text->SetLabel(_("No update available!"));
|
||||
m_gauge->SetValue(100);
|
||||
break;
|
||||
case Result::UpdateAvailable:
|
||||
{
|
||||
if (!m_changelog_filename.empty())
|
||||
{
|
||||
m_changelog->SetURL(fmt::format("https://cemu.info/changelog/{}", m_changelog_filename));
|
||||
m_changelog->Show();
|
||||
}
|
||||
else
|
||||
m_changelog->Hide();
|
||||
|
||||
m_update_button->Show();
|
||||
|
||||
|
||||
m_text->SetLabel(_("Update available!"));
|
||||
m_cancel_button->SetLabel(_("Exit"));
|
||||
break;
|
||||
}
|
||||
case Result::UpdateDownloaded:
|
||||
m_text->SetLabel(_("Extracting update..."));
|
||||
m_gauge->SetValue(0);
|
||||
break;
|
||||
case Result::UpdateDownloadError:
|
||||
m_update_button->Enable();
|
||||
m_text->SetLabel(_("Couldn't download the update!"));
|
||||
break;
|
||||
case Result::ExtractSuccess:
|
||||
m_text->SetLabel(_("Applying update..."));
|
||||
m_gauge->SetValue(0);
|
||||
m_cancel_button->Disable();
|
||||
break;
|
||||
case Result::ExtractError:
|
||||
m_update_button->Enable();
|
||||
m_cancel_button->Enable();
|
||||
m_text->SetLabel(_("Extracting failed!"));
|
||||
break;
|
||||
case Result::Success:
|
||||
m_cancel_button->Enable();
|
||||
m_update_button->Hide();
|
||||
|
||||
m_text->SetLabel(_("Success"));
|
||||
m_cancel_button->SetLabel(_("Restart"));
|
||||
m_restart_required = true;
|
||||
break;
|
||||
default: ;
|
||||
}
|
||||
}
|
||||
|
||||
void CemuUpdateWindow::OnGaugeUpdate(wxCommandEvent& event)
|
||||
{
|
||||
const int total_size = m_gauge_max_value > 0 ? m_gauge_max_value : 10000000;
|
||||
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();
|
||||
|
||||
m_update_button->Disable();
|
||||
|
||||
m_text->SetLabel(_("Downloading update..."));
|
||||
}
|
||||
|
||||
void CemuUpdateWindow::OnCancelButton(const wxCommandEvent& event)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
71
src/gui/CemuUpdateWindow.h
Normal file
71
src/gui/CemuUpdateWindow.h
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#pragma once
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/gauge.h>
|
||||
#include <wx/timer.h>
|
||||
#include <wx/hyperlink.h>
|
||||
#include <wx/checkbox.h>
|
||||
|
||||
#include <curl/system.h>
|
||||
|
||||
class CemuUpdateWindow : public wxDialog
|
||||
{
|
||||
public:
|
||||
CemuUpdateWindow(wxWindow* parent);
|
||||
~CemuUpdateWindow();
|
||||
|
||||
static std::future<bool> IsUpdateAvailable();
|
||||
|
||||
private:
|
||||
wxStaticText* m_text;
|
||||
wxGauge* m_gauge;
|
||||
wxButton* m_cancel_button, *m_update_button;
|
||||
wxHyperlinkCtrl* m_changelog;
|
||||
|
||||
void OnUpdateButton(const wxCommandEvent& event);
|
||||
void OnCancelButton(const wxCommandEvent& event);
|
||||
void OnClose(wxCloseEvent& event);
|
||||
void OnResult(wxCommandEvent& event);
|
||||
void OnGaugeUpdate(wxCommandEvent& event);
|
||||
|
||||
static size_t WriteStringCallback(char* ptr, size_t size, size_t nmemb, void* userdata);
|
||||
static bool GetServerVersion(uint64& version, std::string& filename, std::string& changelog_filename);
|
||||
static bool CheckVersion();
|
||||
static bool IsUpdateAvailable(uint64 latest_version);
|
||||
|
||||
static int ProgressCallback(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
|
||||
bool DownloadCemuZip(const std::string& url, const fs::path& filename);
|
||||
bool ExtractUpdate(const fs::path& zipname, const fs::path& targetpath);
|
||||
|
||||
enum class WorkerOrder
|
||||
{
|
||||
Idle,
|
||||
Exit,
|
||||
CheckVersion,
|
||||
UpdateVersion,
|
||||
};
|
||||
enum class Result
|
||||
{
|
||||
NoUpdateAvailable,
|
||||
UpdateAvailable,
|
||||
UpdateDownloaded,
|
||||
UpdateDownloadError,
|
||||
ExtractSuccess,
|
||||
ExtractError,
|
||||
Success,
|
||||
Error
|
||||
};
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_condition;
|
||||
WorkerOrder m_order = WorkerOrder::CheckVersion;
|
||||
void WorkerThread();
|
||||
|
||||
uint64 m_version = 0;
|
||||
std::string m_filename, m_changelog_filename;
|
||||
int m_gauge_max_value = 0;
|
||||
|
||||
std::thread m_thread;
|
||||
fs::path m_restart_file;
|
||||
bool m_restart_required = false;
|
||||
};
|
||||
788
src/gui/ChecksumTool.cpp
Normal file
788
src/gui/ChecksumTool.cpp
Normal file
|
|
@ -0,0 +1,788 @@
|
|||
#include "gui/ChecksumTool.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <shellapi.h>
|
||||
#endif
|
||||
|
||||
#include "Cafe/TitleList/GameInfo.h"
|
||||
#include "gui/helpers/wxCustomEvents.h"
|
||||
#include "util/helpers/helpers.h"
|
||||
#include "gui/helpers/wxHelpers.h"
|
||||
#include "Cafe/Filesystem/WUD/wud.h"
|
||||
|
||||
#include <zip.h>
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include <openssl/sha.h>
|
||||
#include <rapidjson/document.h>
|
||||
#include <rapidjson/istreamwrapper.h>
|
||||
#include <rapidjson/ostreamwrapper.h>
|
||||
#include <rapidjson/prettywriter.h>
|
||||
#include <rapidjson/schema.h>
|
||||
|
||||
#include <wx/frame.h>
|
||||
#include <wx/translation.h>
|
||||
#include <wx/xrc/xmlres.h>
|
||||
#include <wx/gauge.h>
|
||||
#include <wx/gdicmn.h>
|
||||
#include <wx/string.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/statbox.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/filedlg.h>
|
||||
#include <wx/dirdlg.h>
|
||||
#include <wx/msgdlg.h>
|
||||
|
||||
#include "config/ActiveSettings.h"
|
||||
#include "Cafe/TitleList/TitleList.h"
|
||||
|
||||
const char kSchema[] = R"(
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"region": {
|
||||
"type": "integer"
|
||||
},
|
||||
"version": {
|
||||
"type": "integer"
|
||||
},
|
||||
"wud_hash": {
|
||||
"type": "string"
|
||||
},
|
||||
"files": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file": {
|
||||
"type": "string"
|
||||
},
|
||||
"hash": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"file",
|
||||
"hash"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"title_id",
|
||||
"region",
|
||||
"version",
|
||||
"files"
|
||||
]
|
||||
})";
|
||||
|
||||
|
||||
ChecksumTool::ChecksumTool(wxWindow* parent, wxTitleManagerList::TitleEntry& entry)
|
||||
: wxDialog(parent, wxID_ANY,
|
||||
wxStringFormat2(_("Title checksum of {:08x}-{:08x}"), (uint32)(entry.title_id >> 32), (uint32)(entry.title_id & 0xFFFFFFFF)),
|
||||
wxDefaultPosition, wxDefaultSize, wxCAPTION | wxFRAME_TOOL_WINDOW | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX), m_entry(entry)
|
||||
{
|
||||
|
||||
m_info = CafeTitleList::GetTitleInfoByUID(m_entry.location_uid);
|
||||
if (!m_info.IsValid())
|
||||
throw std::runtime_error("Invalid title");
|
||||
|
||||
// only request online update once
|
||||
static bool s_once = false;
|
||||
if (!s_once)
|
||||
{
|
||||
s_once = true;
|
||||
m_online_ready = std::async(std::launch::async, &ChecksumTool::LoadOnlineData, this);
|
||||
}
|
||||
else
|
||||
m_enable_verify_button = 1;
|
||||
|
||||
auto* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
{
|
||||
auto* box_sizer = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Verifying integrity of game files...")), wxVERTICAL);
|
||||
auto* box = box_sizer->GetStaticBox();
|
||||
|
||||
m_progress = new wxGauge(box, wxID_ANY, 100, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL | wxGA_SMOOTH);
|
||||
m_progress->SetMinSize({ 400, -1 });
|
||||
m_progress->SetValue(0);
|
||||
box_sizer->Add(m_progress, 0, wxALL | wxEXPAND, 5);
|
||||
|
||||
m_status = new wxStaticText(box, wxID_ANY, wxEmptyString);
|
||||
m_status->Wrap(-1);
|
||||
box_sizer->Add(m_status, 0, wxLEFT | wxRIGHT | wxBOTTOM | wxEXPAND, 5);
|
||||
|
||||
sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
|
||||
}
|
||||
|
||||
{
|
||||
auto* box_sizer = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Control")), wxHORIZONTAL);
|
||||
auto* box = box_sizer->GetStaticBox();
|
||||
|
||||
m_verify_online = new wxButton(box, wxID_ANY, _("Verify online"));
|
||||
m_verify_online->SetToolTip(_("Verifies the checksum online"));
|
||||
m_verify_online->Disable();
|
||||
m_verify_online->Bind(wxEVT_BUTTON, &ChecksumTool::OnVerifyOnline, this);
|
||||
m_verify_online->Bind(wxEVT_ENABLE, [this](wxCommandEvent&)
|
||||
{
|
||||
++m_enable_verify_button;
|
||||
if (m_enable_verify_button >= 2)
|
||||
{
|
||||
// only enable if we have a file for it
|
||||
const auto title_id_str = fmt::format("{:016x}", m_json_entry.title_id);
|
||||
const auto default_file = fmt::format("{}_v{}.json", title_id_str, m_info.GetAppTitleVersion());
|
||||
|
||||
const auto checksum_path = ActiveSettings::GetPath("resources/checksums/{}", default_file);
|
||||
if (exists(checksum_path))
|
||||
m_verify_online->Enable();
|
||||
}
|
||||
});
|
||||
box_sizer->Add(m_verify_online, 0, wxALL | wxEXPAND, 5);
|
||||
|
||||
m_verify_local = new wxButton(box, wxID_ANY, _("Verify with local file"));
|
||||
m_verify_online->SetToolTip(_("Verifies the checksum with a local json file you can select"));
|
||||
m_verify_local->Disable();
|
||||
m_verify_local->Bind(wxEVT_BUTTON, &ChecksumTool::OnVerifyLocal, this);
|
||||
box_sizer->Add(m_verify_local, 0, wxALL | wxEXPAND, 5);
|
||||
|
||||
m_export_button = new wxButton(box, wxID_ANY, _("Export"));
|
||||
m_verify_online->SetToolTip(_("Export the title checksum data to a local json file"));
|
||||
m_export_button->Disable();
|
||||
m_export_button->Bind(wxEVT_BUTTON, &ChecksumTool::OnExportChecksums, this);
|
||||
box_sizer->Add(m_export_button, 0, wxALL | wxEXPAND, 5);
|
||||
|
||||
sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
|
||||
}
|
||||
|
||||
this->Bind(wxEVT_SET_GAUGE_VALUE, &ChecksumTool::OnSetGaugevalue, this);
|
||||
|
||||
m_worker = std::thread(&ChecksumTool::DoWork, this);
|
||||
|
||||
this->SetSizerAndFit(sizer);
|
||||
this->Centre(wxBOTH);
|
||||
}
|
||||
|
||||
ChecksumTool::~ChecksumTool()
|
||||
{
|
||||
m_running = false;
|
||||
if (m_worker.joinable())
|
||||
m_worker.join();
|
||||
}
|
||||
|
||||
std::size_t WriteCallback(const char* in, std::size_t size, std::size_t num, std::string* out)
|
||||
{
|
||||
const std::size_t totalBytes(size * num);
|
||||
out->append(in, totalBytes);
|
||||
return totalBytes;
|
||||
}
|
||||
|
||||
void ChecksumTool::LoadOnlineData() const
|
||||
{
|
||||
try
|
||||
{
|
||||
bool updated_required = true;
|
||||
|
||||
std::string latest_commit;
|
||||
|
||||
const auto checksum_path = ActiveSettings::GetPath("resources/checksums");
|
||||
if (exists(checksum_path))
|
||||
{
|
||||
std::string current_commit;
|
||||
// check for current version
|
||||
std::ifstream file(checksum_path / "commit.txt");
|
||||
if (file.is_open())
|
||||
{
|
||||
std::getline(file, current_commit);
|
||||
file.close();
|
||||
}
|
||||
|
||||
// check latest version
|
||||
/*
|
||||
https://api.github.com/repos/teamcemu/title-checksums/branches/master
|
||||
https://api.github.com/repos/teamcemu/title-checksums/commits?per_page=1
|
||||
*/
|
||||
std::string data;
|
||||
auto* curl = curl_easy_init();
|
||||
curl_easy_setopt(curl, CURLOPT_URL, "https://api.github.com/repos/teamcemu/title-checksums/commits/master");
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, fmt::format("Cemu_{}.{}{}", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_SUFFIX).c_str());
|
||||
|
||||
curl_easy_perform(curl);
|
||||
|
||||
long http_code = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
||||
curl_easy_cleanup(curl);
|
||||
if (http_code == 200 && !data.empty())
|
||||
{
|
||||
rapidjson::Document doc;
|
||||
doc.Parse(data.c_str(), data.size());
|
||||
if (!doc.HasParseError() && doc.HasMember("sha"))
|
||||
{
|
||||
latest_commit = doc["sha"].GetString();
|
||||
if (boost::iequals(current_commit, latest_commit))
|
||||
{
|
||||
updated_required = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// create directory since not available yet
|
||||
fs::create_directories(checksum_path);
|
||||
}
|
||||
|
||||
if (updated_required)
|
||||
{
|
||||
std::string data;
|
||||
auto* curl = curl_easy_init();
|
||||
curl_easy_setopt(curl, CURLOPT_URL, "https://github.com/TeamCemu/title-checksums/archive/master.zip");
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, fmt::format("Cemu_{}.{}{}", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_SUFFIX).c_str());
|
||||
|
||||
curl_easy_perform(curl);
|
||||
|
||||
long http_code = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
||||
curl_easy_cleanup(curl);
|
||||
if (http_code == 200 && !data.empty())
|
||||
{
|
||||
// init zip source
|
||||
zip_error_t error;
|
||||
zip_error_init(&error);
|
||||
zip_source_t* src;
|
||||
if ((src = zip_source_buffer_create(data.data(), data.size(), 1, &error)) == nullptr)
|
||||
{
|
||||
zip_error_fini(&error);
|
||||
return;
|
||||
}
|
||||
|
||||
auto* za = zip_open_from_source(src, ZIP_RDONLY, &error);
|
||||
if (!za)
|
||||
{
|
||||
wxQueueEvent(m_verify_online, new wxCommandEvent(wxEVT_ENABLE));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto numEntries = zip_get_num_entries(za, 0);
|
||||
for (sint64 i = 0; i < numEntries; i++)
|
||||
{
|
||||
zip_stat_t sb = { 0 };
|
||||
if (zip_stat_index(za, i, 0, &sb) != 0)
|
||||
continue;
|
||||
|
||||
if (std::strstr(sb.name, "../") != nullptr ||
|
||||
std::strstr(sb.name, "..\\") != nullptr)
|
||||
continue; // bad path
|
||||
|
||||
if (boost::equals(sb.name, "title-checksums-master/"))
|
||||
continue;
|
||||
|
||||
// title-checksums-master/
|
||||
const auto path = checksum_path / &sb.name[sizeof("title-checksums-master")];
|
||||
|
||||
size_t sbNameLen = strlen(sb.name);
|
||||
if (sbNameLen == 0)
|
||||
continue;
|
||||
|
||||
if (sb.name[sbNameLen - 1] == '/')
|
||||
{
|
||||
std::error_code ec;
|
||||
fs::create_directories(path, ec);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sb.size == 0)
|
||||
continue;
|
||||
|
||||
if (sb.size > (1024 * 1024 * 128))
|
||||
continue; // skip unusually huge files
|
||||
|
||||
zip_file_t* zipFile = zip_fopen_index(za, i, 0);
|
||||
if (zipFile == nullptr)
|
||||
continue;
|
||||
|
||||
std::vector<char> buffer(sb.size);
|
||||
if (zip_fread(zipFile, buffer.data(), sb.size) == sb.size)
|
||||
{
|
||||
std::ofstream file(path);
|
||||
if (file.is_open())
|
||||
{
|
||||
file.write(buffer.data(), sb.size);
|
||||
file.flush();
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
zip_fclose(zipFile);
|
||||
}
|
||||
|
||||
std::ofstream file(checksum_path / "commit.txt");
|
||||
if (file.is_open())
|
||||
{
|
||||
file << latest_commit;
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(const std::exception& ex)
|
||||
{
|
||||
forceLog_printf("error on updating json checksum data: %s", ex.what());
|
||||
}
|
||||
|
||||
wxQueueEvent(m_verify_online, new wxCommandEvent(wxEVT_ENABLE));
|
||||
}
|
||||
|
||||
void ChecksumTool::OnSetGaugevalue(wxSetGaugeValue& event)
|
||||
{
|
||||
event.GetGauge()->SetValue(event.GetValue());
|
||||
event.GetTextCtrl()->SetLabelText(event.GetText());
|
||||
|
||||
// no error
|
||||
if(event.GetInt() == 0 && event.GetValue() == 100)
|
||||
{
|
||||
m_export_button->Enable();
|
||||
m_verify_local->Enable();
|
||||
wxPostEvent(m_verify_online, wxCommandEvent(wxEVT_ENABLE));
|
||||
}
|
||||
}
|
||||
|
||||
void ChecksumTool::OnExportChecksums(wxCommandEvent& event)
|
||||
{
|
||||
// TODO: merge if json already exists
|
||||
wxDirDialog dialog(this, _("Export checksum entry"), "", wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST);
|
||||
if (dialog.ShowModal() != wxID_OK || dialog.GetPath().IsEmpty())
|
||||
return;
|
||||
|
||||
rapidjson::Document doc;
|
||||
doc.SetObject();
|
||||
auto& a = doc.GetAllocator();
|
||||
/*
|
||||
title_id
|
||||
region
|
||||
version
|
||||
wud_hash
|
||||
files:
|
||||
[
|
||||
{file, hash}
|
||||
]
|
||||
*/
|
||||
|
||||
auto title_id_str = fmt::format("{:016x}", m_json_entry.title_id);
|
||||
doc.AddMember("title_id", rapidjson::StringRef(title_id_str.c_str(), title_id_str.size()), a);
|
||||
doc.AddMember("region", (int)m_info.GetMetaRegion(), a);
|
||||
doc.AddMember("version", m_info.GetAppTitleVersion(), a);
|
||||
if (!m_json_entry.wud_hash.empty())
|
||||
doc.AddMember("wud_hash", rapidjson::StringRef(m_json_entry.wud_hash.c_str(), m_json_entry.wud_hash.size()), a);
|
||||
|
||||
rapidjson::Value entry_array(rapidjson::kArrayType);
|
||||
|
||||
rapidjson::Value file_array(rapidjson::kArrayType);
|
||||
for(const auto& file : m_json_entry.file_hashes)
|
||||
{
|
||||
rapidjson::Value file_entry;
|
||||
file_entry.SetObject();
|
||||
|
||||
file_entry.AddMember("file", rapidjson::StringRef(file.first.c_str(), file.first.size()), a);
|
||||
file_entry.AddMember("hash", rapidjson::StringRef(file.second.c_str(), file.second.size()), a);
|
||||
|
||||
file_array.PushBack(file_entry, a);
|
||||
}
|
||||
|
||||
doc.AddMember("files", file_array, a);
|
||||
|
||||
std::filesystem::path target_file{ dialog.GetPath().c_str().AsInternal() };
|
||||
target_file /= fmt::format("{}_v{}.json", title_id_str, m_info.GetAppTitleVersion());
|
||||
|
||||
std::ofstream file(target_file);
|
||||
if(file.is_open())
|
||||
{
|
||||
rapidjson::OStreamWrapper osw(file);
|
||||
rapidjson::PrettyWriter<rapidjson::OStreamWrapper> writer(osw);
|
||||
//rapidjson::GenericSchemaValidator<rapidjson::SchemaDocument, rapidjson::Writer<rapidjson::StringBuffer> > validator(schema, writer);
|
||||
doc.Accept(writer);
|
||||
wxMessageBox(_("Export successful"), wxMessageBoxCaptionStr, wxOK | wxCENTRE, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
wxMessageBox(wxStringFormat2(_("Can't write to file: {}"), target_file.string()), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
}
|
||||
}
|
||||
|
||||
void ChecksumTool::VerifyJsonEntry(const rapidjson::Document& doc)
|
||||
{
|
||||
rapidjson::Document sdoc;
|
||||
sdoc.Parse(kSchema, std::size(kSchema));
|
||||
wxASSERT(!sdoc.HasParseError());
|
||||
rapidjson::SchemaDocument schema(sdoc);
|
||||
rapidjson::SchemaValidator validator(schema);
|
||||
if (!doc.Accept(validator))
|
||||
{
|
||||
//// validation error:
|
||||
//rapidjson::StringBuffer sb;
|
||||
//validator.GetInvalidSchemaPointer().StringifyUriFragment(sb);
|
||||
//printf("Invalid schema: %s\n", sb.GetString());
|
||||
//printf("Invalid keyword: %s\n", validator.GetInvalidSchemaKeyword());
|
||||
//sb.Clear();
|
||||
//validator.GetInvalidDocumentPointer().StringifyUriFragment(sb);
|
||||
//printf("Invalid document: %s\n", sb.GetString());
|
||||
///*
|
||||
//Invalid schema: #
|
||||
//Invalid keyword: required
|
||||
//Invalid document: #
|
||||
// */
|
||||
|
||||
wxMessageBox(_("JSON file doesn't satisfy needed schema"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
JsonEntry test_entry{};
|
||||
test_entry.title_id = ConvertString<uint64>(doc["title_id"].GetString(), 16);
|
||||
test_entry.region = (CafeConsoleRegion)doc["region"].GetInt();
|
||||
test_entry.version = doc["version"].GetInt();
|
||||
if (doc.HasMember("wud_hash"))
|
||||
test_entry.wud_hash = doc["wud_hash"].GetString();
|
||||
|
||||
for (const auto& v : doc["files"].GetArray())
|
||||
{
|
||||
std::filesystem::path genericFilePath(v["file"].GetString(), std::filesystem::path::generic_format); // convert path to generic form (forward slashes)
|
||||
test_entry.file_hashes[genericFilePath.generic_string()] = v["hash"].GetString();
|
||||
}
|
||||
|
||||
if (m_json_entry.title_id != test_entry.title_id)
|
||||
{
|
||||
wxMessageBox(wxStringFormat2(_("The file you are comparing with is for a different title.")), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
return;
|
||||
}
|
||||
if (m_json_entry.version != test_entry.version)
|
||||
{
|
||||
wxMessageBox(wxStringFormat2(_("Wrong version: {}"), test_entry.version), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
return;
|
||||
}
|
||||
if (m_json_entry.region != test_entry.region)
|
||||
{
|
||||
wxMessageBox(wxStringFormat2(_("Wrong region: {}"), test_entry.region), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
return;
|
||||
}
|
||||
if (!m_json_entry.wud_hash.empty())
|
||||
{
|
||||
if (test_entry.wud_hash.empty())
|
||||
{
|
||||
wxMessageBox(_("The verification data doesn't include a wud hash!"), _("Error"), wxOK | wxCENTRE | wxICON_WARNING, this);
|
||||
return;
|
||||
}
|
||||
if(!boost::iequals(test_entry.wud_hash, m_json_entry.wud_hash))
|
||||
{
|
||||
wxMessageBox(wxStringFormat2(_("Your game image is invalid!\n\nYour hash:\n{}\n\nExpected hash:\n{}"), m_json_entry.wud_hash, test_entry.wud_hash), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::map<std::string_view, std::pair<std::string, std::string>> invalid_hashes;
|
||||
std::vector<std::string_view> missing_files;
|
||||
const auto writeMismatchInfoToLog = [this, &missing_files, &invalid_hashes]()
|
||||
{
|
||||
wxFileDialog dialog(this, _("Select a file to export the errors"), wxEmptyString, wxEmptyString, "Error list (*.txt)|*.txt", wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
|
||||
if (dialog.ShowModal() != wxID_OK || dialog.GetPath().IsEmpty())
|
||||
return;
|
||||
|
||||
const std::string path = dialog.GetPath().ToUTF8().data();
|
||||
std::ofstream file(path);
|
||||
if (file.is_open())
|
||||
{
|
||||
if (!missing_files.empty())
|
||||
{
|
||||
file << "The following files are missing:\n";
|
||||
for (const auto& f : missing_files)
|
||||
file << "\t" << f << "\n";
|
||||
|
||||
file << "\n";
|
||||
}
|
||||
|
||||
if (!invalid_hashes.empty())
|
||||
{
|
||||
file << "The following files have an invalid hash (name | current hash | expected hash):\n";
|
||||
for (const auto& f : invalid_hashes)
|
||||
file << "\t" << f.first << " | " << f.second.first << " | " << f.second.second << "\n";
|
||||
}
|
||||
file.flush();
|
||||
file.close();
|
||||
#ifdef _WIN32
|
||||
ShellExecuteA(GetHWND(), "open", path.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
|
||||
#else
|
||||
assert_dbg();
|
||||
#endif
|
||||
}
|
||||
else
|
||||
wxMessageBox(_("Can't open file to write!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
};
|
||||
|
||||
for (const auto& f : test_entry.file_hashes)
|
||||
{
|
||||
const auto it = m_json_entry.file_hashes.find(f.first);
|
||||
if (it == m_json_entry.file_hashes.cend())
|
||||
{
|
||||
missing_files.emplace_back(f.first);
|
||||
}
|
||||
else if (!boost::iequals(f.second, it->second))
|
||||
{
|
||||
invalid_hashes[f.first] = std::make_pair(it->second, f.second);
|
||||
}
|
||||
}
|
||||
|
||||
// files are missing but rest is okay
|
||||
if ((!missing_files.empty() || !invalid_hashes.empty()) && (missing_files.size() + invalid_hashes.size()) < 30)
|
||||
{
|
||||
// the list of missing + invalid hashes is short enough that we can print it to the message box
|
||||
std::stringstream str;
|
||||
if (missing_files.size() > 0)
|
||||
{
|
||||
str << _("The following files are missing:").ToUTF8().data() << "\n";
|
||||
for (const auto& v : missing_files)
|
||||
str << v << "\n";
|
||||
if(invalid_hashes.size() > 0)
|
||||
str << "\n";
|
||||
}
|
||||
if (invalid_hashes.size() > 0)
|
||||
{
|
||||
str << _("The following files are damaged:").ToUTF8().data() << "\n";
|
||||
for (const auto& v : invalid_hashes)
|
||||
str << v.first << "\n";
|
||||
}
|
||||
|
||||
wxMessageBox(str.str(), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
return;
|
||||
}
|
||||
else if (missing_files.empty() && !invalid_hashes.empty())
|
||||
{
|
||||
const int result = wxMessageBox(wxStringFormat2(_("{} files have an invalid hash!\nDo you want to export a list of them to a file?"), invalid_hashes.size()), _("Error"), wxYES_NO | wxCENTRE | wxICON_ERROR, this);
|
||||
if (result == wxYES)
|
||||
{
|
||||
writeMismatchInfoToLog();
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (!missing_files.empty() && !invalid_hashes.empty())
|
||||
{
|
||||
const int result = wxMessageBox(wxStringFormat2(_("Multiple issues with your game files have been found!\nDo you want to export them to a file?"), invalid_hashes.size()), _("Error"), wxYES_NO | wxCENTRE | wxICON_ERROR, this);
|
||||
if (result == wxYES)
|
||||
{
|
||||
writeMismatchInfoToLog();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
wxMessageBox(_("Your game files are valid"), _("Success"), wxOK | wxCENTRE, this);
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
wxMessageBox(wxStringFormat2(_("JSON parse error: {}"), ex.what()), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ChecksumTool::OnVerifyOnline(wxCommandEvent& event)
|
||||
{
|
||||
const auto title_id_str = fmt::format("{:016x}", m_json_entry.title_id);
|
||||
const auto default_file = fmt::format("{}_v{}.json", title_id_str, m_info.GetAppTitleVersion());
|
||||
|
||||
const auto checksum_path = ActiveSettings::GetPath("resources/checksums/{}", default_file);
|
||||
if(!exists(checksum_path))
|
||||
return;
|
||||
|
||||
std::ifstream file(checksum_path);
|
||||
if (!file.is_open())
|
||||
{
|
||||
wxMessageBox(_("Can't open file!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
return;
|
||||
}
|
||||
|
||||
rapidjson::IStreamWrapper str(file);
|
||||
rapidjson::Document d;
|
||||
d.ParseStream(str);
|
||||
if (d.HasParseError())
|
||||
{
|
||||
wxMessageBox(_("Can't parse json file!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
return;
|
||||
}
|
||||
|
||||
VerifyJsonEntry(d);
|
||||
}
|
||||
|
||||
void ChecksumTool::OnVerifyLocal(wxCommandEvent& event)
|
||||
{
|
||||
const auto title_id_str = fmt::format("{:016x}", m_json_entry.title_id);
|
||||
const auto default_file = fmt::format("{}_v{}.json", title_id_str, m_info.GetAppTitleVersion());
|
||||
wxFileDialog file_dialog(this, _("Open checksum entry"), "", default_file.c_str(),"JSON files (*.json)|*.json", wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if (file_dialog.ShowModal() != wxID_OK || file_dialog.GetPath().IsEmpty())
|
||||
return;
|
||||
|
||||
std::filesystem::path filename{ file_dialog.GetPath().c_str().AsInternal() };
|
||||
std::ifstream file(filename);
|
||||
if(!file.is_open())
|
||||
{
|
||||
wxMessageBox(_("Can't open file!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
return;
|
||||
}
|
||||
|
||||
rapidjson::IStreamWrapper str(file);
|
||||
rapidjson::Document d;
|
||||
d.ParseStream(str);
|
||||
if (d.HasParseError())
|
||||
{
|
||||
wxMessageBox(_("Can't parse json file!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
return;
|
||||
}
|
||||
|
||||
VerifyJsonEntry(d);
|
||||
}
|
||||
|
||||
static void _fscGetAllFiles(std::set<std::string>& allFilesOut, const std::string& fscBasePath, const std::string& relativePath)
|
||||
{
|
||||
sint32 fscStatus;
|
||||
FSCVirtualFile* fsc = fsc_openDirIterator((fscBasePath + relativePath).c_str(), &fscStatus);
|
||||
cemu_assert(fsc);
|
||||
FSCDirEntry dirEntry;
|
||||
while (fsc_nextDir(fsc, &dirEntry))
|
||||
{
|
||||
if (dirEntry.isDirectory)
|
||||
{
|
||||
_fscGetAllFiles(allFilesOut, fscBasePath, std::string(relativePath).append(dirEntry.GetPath()).append("/"));
|
||||
}
|
||||
else
|
||||
{
|
||||
allFilesOut.emplace(std::string(relativePath).append(dirEntry.GetPath()));
|
||||
}
|
||||
}
|
||||
delete fsc;
|
||||
}
|
||||
|
||||
void ChecksumTool::DoWork()
|
||||
{
|
||||
m_json_entry.title_id = m_info.GetAppTitleId();
|
||||
m_json_entry.region = m_info.GetMetaRegion();
|
||||
m_json_entry.version = m_info.GetAppTitleVersion();
|
||||
|
||||
static_assert(SHA256_DIGEST_LENGTH == 32);
|
||||
|
||||
std::array<uint8, SHA256_DIGEST_LENGTH> checksum{};
|
||||
|
||||
switch (m_info.GetFormat())
|
||||
{
|
||||
case TitleInfo::TitleDataFormat::WUD:
|
||||
{
|
||||
const auto path = m_entry.path.string();
|
||||
wxQueueEvent(this, new wxSetGaugeValue(1, m_progress, m_status, wxStringFormat2(_("Reading game image: {}"), path)));
|
||||
|
||||
wud_t* wud = wud_open(m_info.GetPath());
|
||||
if (!wud)
|
||||
throw std::runtime_error("can't open game image");
|
||||
|
||||
const auto wud_size = wud_getWUDSize(wud);
|
||||
std::vector<uint8> buffer(1024 * 1024 * 8);
|
||||
|
||||
SHA256_CTX sha256;
|
||||
SHA256_Init(&sha256);
|
||||
|
||||
uint32 read = 0;
|
||||
size_t offset = 0;
|
||||
auto size = wud_size;
|
||||
do
|
||||
{
|
||||
if (!m_running.load(std::memory_order_relaxed))
|
||||
{
|
||||
wud_close(wud);
|
||||
return;
|
||||
}
|
||||
|
||||
read = wud_readData(wud, buffer.data(), std::min(buffer.size(), (size_t)wud_size - offset), offset);
|
||||
offset += read;
|
||||
size -= read;
|
||||
|
||||
SHA256_Update(&sha256, buffer.data(), read);
|
||||
|
||||
wxQueueEvent(this, new wxSetGaugeValue((int)((offset * 90) / wud_size), m_progress, m_status, wxStringFormat2(_("Reading game image: {}/{}kb"), offset / 1024, wud_size / 1024)));
|
||||
} while (read != 0 && size > 0);
|
||||
wud_close(wud);
|
||||
|
||||
wxQueueEvent(this, new wxSetGaugeValue(90, m_progress, m_status, wxStringFormat2(_("Generating checksum of game image: {}"), path)));
|
||||
|
||||
if (!m_running.load(std::memory_order_relaxed))
|
||||
return;
|
||||
|
||||
SHA256_Final(checksum.data(), &sha256);
|
||||
|
||||
std::stringstream str;
|
||||
for (const auto& b : checksum)
|
||||
{
|
||||
str << fmt::format("{:02X}", b);
|
||||
}
|
||||
|
||||
m_json_entry.wud_hash = str.str();
|
||||
|
||||
wxQueueEvent(this, new wxSetGaugeValue(100, m_progress, m_status, wxStringFormat2(_("Generated checksum of game image: {}"), path)));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// we hash the individual files for all formats except WUD/WUX
|
||||
std::string temporaryMountPath = TitleInfo::GetUniqueTempMountingPath();
|
||||
m_info.Mount(temporaryMountPath.c_str(), "", FSC_PRIORITY_BASE);
|
||||
wxQueueEvent(this, new wxSetGaugeValue(1, m_progress, m_status, _("Grabbing game files")));
|
||||
|
||||
// get list of all files
|
||||
std::set<std::string> files;
|
||||
_fscGetAllFiles(files, temporaryMountPath, "");
|
||||
|
||||
const size_t file_count = files.size();
|
||||
size_t counter = 0;
|
||||
for (const auto& filename : files)
|
||||
{
|
||||
auto fileData = fsc_extractFile((temporaryMountPath + "/" + filename).c_str());
|
||||
if (!fileData)
|
||||
{
|
||||
cemuLog_log(LogType::Force, "Failed to open {}", filename);
|
||||
continue;
|
||||
}
|
||||
|
||||
SHA256_CTX sha256;
|
||||
SHA256_Init(&sha256);
|
||||
SHA256_Update(&sha256, fileData->data(), fileData->size());
|
||||
SHA256_Final(checksum.data(), &sha256);
|
||||
|
||||
std::stringstream str;
|
||||
for (const auto& b : checksum)
|
||||
{
|
||||
str << fmt::format("{:02X}", b);
|
||||
}
|
||||
|
||||
// store relative path and hash
|
||||
m_json_entry.file_hashes[filename] = str.str();
|
||||
|
||||
++counter;
|
||||
wxQueueEvent(this, new wxSetGaugeValue((int)((counter * 100) / file_count), m_progress, m_status, wxStringFormat2(_("Hashing game file: {}/{}"), counter, file_count)));
|
||||
|
||||
if (!m_running.load(std::memory_order_relaxed))
|
||||
{
|
||||
m_info.Unmount(temporaryMountPath.c_str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
m_info.Unmount(temporaryMountPath.c_str());
|
||||
|
||||
wxQueueEvent(this, new wxSetGaugeValue(100, m_progress, m_status, wxStringFormat2(_("Generated checksum of {} game files"), file_count)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
51
src/gui/ChecksumTool.h
Normal file
51
src/gui/ChecksumTool.h
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#pragma once
|
||||
#include <wx/dialog.h>
|
||||
#include "gui/components/wxTitleManagerList.h"
|
||||
#include "Cafe/TitleList/TitleInfo.h"
|
||||
#include <rapidjson/document.h>
|
||||
|
||||
class wxSetGaugeValue;
|
||||
|
||||
class ChecksumTool : public wxDialog
|
||||
{
|
||||
public:
|
||||
ChecksumTool(wxWindow* parent, wxTitleManagerList::TitleEntry& entry);
|
||||
~ChecksumTool();
|
||||
|
||||
private:
|
||||
std::future<void> m_online_ready;
|
||||
void LoadOnlineData() const;
|
||||
void VerifyJsonEntry(const rapidjson::Document& doc);
|
||||
|
||||
void OnSetGaugevalue(wxSetGaugeValue& event);
|
||||
void OnExportChecksums(wxCommandEvent& event);
|
||||
void OnVerifyOnline(wxCommandEvent& event);
|
||||
void OnVerifyLocal(wxCommandEvent& event);
|
||||
|
||||
void DoWork();
|
||||
std::atomic_bool m_running = true;
|
||||
std::thread m_worker;
|
||||
|
||||
class wxGauge* m_progress;
|
||||
class wxStaticText* m_status;
|
||||
class wxButton *m_verify_online, *m_verify_local, *m_export_button;
|
||||
int m_enable_verify_button = 0;
|
||||
|
||||
TitleInfo m_info;
|
||||
wxTitleManagerList::TitleEntry m_entry;
|
||||
wxColour m_default_color;
|
||||
|
||||
struct JsonEntry
|
||||
{
|
||||
uint64 title_id;
|
||||
uint32 version;
|
||||
CafeConsoleRegion region;
|
||||
|
||||
std::string wud_hash;
|
||||
std::map<std::string, std::string> file_hashes;
|
||||
};
|
||||
JsonEntry m_json_entry;
|
||||
inline static std::mutex s_mutex{};
|
||||
inline static std::vector<JsonEntry> s_entries{};
|
||||
|
||||
};
|
||||
408
src/gui/DownloadGraphicPacksWindow.cpp
Normal file
408
src/gui/DownloadGraphicPacksWindow.cpp
Normal file
|
|
@ -0,0 +1,408 @@
|
|||
#include "gui/wxgui.h"
|
||||
#include "gui/DownloadGraphicPacksWindow.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <curl/curl.h>
|
||||
#include <zip.h>
|
||||
#include <rapidjson/document.h>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "config/ActiveSettings.h"
|
||||
#include "Common/filestream.h"
|
||||
|
||||
#include "Cafe/CafeSystem.h"
|
||||
|
||||
struct DownloadGraphicPacksWindow::curlDownloadFileState_t
|
||||
{
|
||||
std::vector<uint8> fileData;
|
||||
double progress;
|
||||
};
|
||||
|
||||
size_t DownloadGraphicPacksWindow::curlDownloadFile_writeData(void *ptr, size_t size, size_t nmemb, curlDownloadFileState_t* downloadState)
|
||||
{
|
||||
const size_t writeSize = size * nmemb;
|
||||
const size_t currentSize = downloadState->fileData.size();
|
||||
const size_t newSize = currentSize + writeSize;
|
||||
downloadState->fileData.resize(newSize);
|
||||
memcpy(downloadState->fileData.data() + currentSize, ptr, writeSize);
|
||||
return writeSize;
|
||||
}
|
||||
|
||||
int DownloadGraphicPacksWindow::progress_callback(curlDownloadFileState_t* downloadState, double dltotal, double dlnow, double ultotal, double ulnow)
|
||||
{
|
||||
if (dltotal > 1.0)
|
||||
downloadState->progress = dlnow / dltotal;
|
||||
else
|
||||
downloadState->progress = 0.0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool DownloadGraphicPacksWindow::curlDownloadFile(const char *url, curlDownloadFileState_t* downloadState)
|
||||
{
|
||||
CURL* curl = curl_easy_init();
|
||||
if (curl == nullptr)
|
||||
return false;
|
||||
|
||||
downloadState->progress = 0.0;
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlDownloadFile_writeData);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, downloadState);
|
||||
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progress_callback);
|
||||
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, downloadState);
|
||||
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
|
||||
char temp[128];
|
||||
sprintf(temp, "Cemu_%d.%d%s", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_SUFFIX);
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, temp);
|
||||
downloadState->fileData.resize(0);
|
||||
const CURLcode res = curl_easy_perform(curl);
|
||||
curl_easy_cleanup(curl);
|
||||
return res == CURLE_OK;
|
||||
}
|
||||
|
||||
// returns true if the version matches
|
||||
bool checkGraphicPackDownloadedVersion(const char* nameVersion, bool& hasVersionFile)
|
||||
{
|
||||
hasVersionFile = false;
|
||||
const auto path = ActiveSettings::GetPath("graphicPacks/downloadedGraphicPacks/version.txt");
|
||||
std::unique_ptr<FileStream> file(FileStream::openFile2(path));
|
||||
|
||||
std::string versionInFile;
|
||||
if (file && file->readLine(versionInFile))
|
||||
{
|
||||
return boost::iequals(versionInFile, nameVersion);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void createGraphicPackDownloadedVersionFile(const char* nameVersion)
|
||||
{
|
||||
const auto path = ActiveSettings::GetPath("graphicPacks/downloadedGraphicPacks/version.txt");
|
||||
std::unique_ptr<FileStream> file(FileStream::createFile2(path));
|
||||
if (file)
|
||||
file->writeString(nameVersion);
|
||||
else
|
||||
{
|
||||
cemuLog_force("Failed to write graphic pack version.txt");
|
||||
}
|
||||
}
|
||||
|
||||
void deleteDownloadedGraphicPacks()
|
||||
{
|
||||
const auto path = ActiveSettings::GetPath("graphicPacks/downloadedGraphicPacks");
|
||||
std::error_code er;
|
||||
if (!fs::exists(path))
|
||||
return;
|
||||
try
|
||||
{
|
||||
for (auto& p : fs::directory_iterator(path))
|
||||
{
|
||||
fs::remove_all(p.path(), er);
|
||||
}
|
||||
}
|
||||
catch (std::filesystem::filesystem_error& e)
|
||||
{
|
||||
cemuLog_log(LogType::Force, "Error in deleteDownloadedGraphicPacks():");
|
||||
cemuLog_log(LogType::Force, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadGraphicPacksWindow::UpdateThread()
|
||||
{
|
||||
if (CafeSystem::IsTitleRunning())
|
||||
{
|
||||
wxMessageBox(_("Graphic packs cannot be updated while a game is running."), _("Graphic packs"), 5, this->GetParent());
|
||||
// cancel update
|
||||
m_threadState = ThreadFinished;
|
||||
return;
|
||||
}
|
||||
|
||||
// get github url
|
||||
std::string githubAPIUrl;
|
||||
curlDownloadFileState_t tempDownloadState;
|
||||
std::string queryUrl("http://cemu.info/api/query_graphicpack_url_1_17_0.php?");
|
||||
char temp[64];
|
||||
sprintf(temp, "version=%d.%d.%d%s", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_SUFFIX);
|
||||
queryUrl.append(temp);
|
||||
queryUrl.append("&");
|
||||
sprintf(temp, "t=%u", (uint32)std::chrono::seconds(std::time(NULL)).count()); // add a dynamic part to the url to bypass overly aggressive caching (like some proxies do)
|
||||
queryUrl.append(temp);
|
||||
if (curlDownloadFile(queryUrl.c_str(), &tempDownloadState) && boost::starts_with((const char*)tempDownloadState.fileData.data(), "http"))
|
||||
{
|
||||
// convert downloaded data to url string
|
||||
githubAPIUrl.assign(tempDownloadState.fileData.cbegin(), tempDownloadState.fileData.cend());
|
||||
}
|
||||
else
|
||||
{
|
||||
// cemu api request failed, use hardcoded github url
|
||||
forceLog_printf("Graphic pack update request failed or returned invalid URL. Using default repository URL instead");
|
||||
githubAPIUrl = "https://api.github.com/repos/slashiee/cemu_graphic_packs/releases/latest";
|
||||
}
|
||||
// github API request
|
||||
if (curlDownloadFile(githubAPIUrl.c_str(), &tempDownloadState) == false)
|
||||
{
|
||||
wxMessageBox( _("Error"), _(L"Failed to connect to server"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
m_threadState = ThreadError;
|
||||
return;
|
||||
}
|
||||
// parse json result
|
||||
rapidjson::Document d;
|
||||
d.Parse((const char*)tempDownloadState.fileData.data(), tempDownloadState.fileData.size());
|
||||
if (d.HasParseError())
|
||||
{
|
||||
m_threadState = ThreadError;
|
||||
return;
|
||||
}
|
||||
auto& jsonName = d["name"];
|
||||
if (jsonName.IsString() == false)
|
||||
{
|
||||
m_threadState = ThreadError;
|
||||
return;
|
||||
}
|
||||
const char* assetName = jsonName.GetString(); // name includes version
|
||||
if( d.IsObject() == false)
|
||||
{
|
||||
m_threadState = ThreadError;
|
||||
return;
|
||||
}
|
||||
auto& jsonAssets = d["assets"];
|
||||
if (jsonAssets.IsArray() == false || jsonAssets.GetArray().Size() == 0)
|
||||
{
|
||||
m_threadState = ThreadError;
|
||||
return;
|
||||
}
|
||||
auto& jsonAsset0 = jsonAssets.GetArray()[0];
|
||||
if (jsonAsset0.IsObject() == false)
|
||||
{
|
||||
m_threadState = ThreadError;
|
||||
return;
|
||||
}
|
||||
auto& jsonDownloadUrl = jsonAsset0["browser_download_url"];
|
||||
if (jsonDownloadUrl.IsString() == false)
|
||||
{
|
||||
m_threadState = ThreadError;
|
||||
return;
|
||||
}
|
||||
const char* browserDownloadUrl = jsonDownloadUrl.GetString();
|
||||
// check version
|
||||
bool hasVersionFile = false;
|
||||
if (checkGraphicPackDownloadedVersion(assetName, hasVersionFile))
|
||||
{
|
||||
// already up to date
|
||||
wxMessageBox(_("No updates available."), _("Graphic packs"), wxOK | wxCENTRE, this->GetParent());
|
||||
m_threadState = ThreadFinished;
|
||||
return;
|
||||
}
|
||||
if (hasVersionFile)
|
||||
{
|
||||
// if a version file already exists (and graphic packs are installed) ask the user if he really wants to update
|
||||
if (wxMessageBox(_("Updated graphic packs are available. Do you want to download and install them?"), _("Graphic packs"), wxYES_NO, this->GetParent()) != wxYES)
|
||||
{
|
||||
// cancel update
|
||||
m_threadState = ThreadFinished;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// download zip
|
||||
m_stage = StageDownloading;
|
||||
if (curlDownloadFile(browserDownloadUrl, m_downloadState.get()) == false)
|
||||
{
|
||||
wxMessageBox(_("Error"), _(L"Failed to connect to server"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
m_threadState = ThreadError;
|
||||
return;
|
||||
}
|
||||
|
||||
m_extractionProgress = 0.0;
|
||||
m_stage = StageExtracting;
|
||||
|
||||
zip_source_t *src;
|
||||
zip_t *za;
|
||||
zip_error_t error;
|
||||
|
||||
// init zip source
|
||||
zip_error_init(&error);
|
||||
if ((src = zip_source_buffer_create(m_downloadState->fileData.data(), m_downloadState->fileData.size(), 1, &error)) == NULL)
|
||||
{
|
||||
zip_error_fini(&error);
|
||||
m_threadState = ThreadError;
|
||||
return;
|
||||
}
|
||||
|
||||
// open zip from source
|
||||
if ((za = zip_open_from_source(src, 0, &error)) == NULL)
|
||||
{
|
||||
zip_source_free(src);
|
||||
zip_error_fini(&error);
|
||||
m_threadState = ThreadError;
|
||||
return;
|
||||
}
|
||||
|
||||
auto path = ActiveSettings::GetPath("graphicPacks/downloadedGraphicPacks");
|
||||
std::error_code er;
|
||||
//fs::remove_all(path, er); -> Don't delete the whole folder and recreate it immediately afterwards because sometimes it just fails
|
||||
deleteDownloadedGraphicPacks();
|
||||
fs::create_directories(path, er); // make sure downloadedGraphicPacks folder exists
|
||||
|
||||
sint32 numEntries = zip_get_num_entries(za, 0);
|
||||
for (sint32 i = 0; i < numEntries; i++)
|
||||
{
|
||||
m_extractionProgress = (double)i / (double)numEntries;
|
||||
zip_stat_t sb = { 0 };
|
||||
if (zip_stat_index(za, i, 0, &sb) != 0)
|
||||
{
|
||||
assert_dbg();
|
||||
}
|
||||
|
||||
if(std::strstr(sb.name, "../") != nullptr ||
|
||||
std::strstr(sb.name, "..\\") != nullptr)
|
||||
continue; // bad path
|
||||
|
||||
path = ActiveSettings::GetPath("graphicPacks/downloadedGraphicPacks/{}", sb.name);
|
||||
|
||||
size_t sbNameLen = strlen(sb.name);
|
||||
if(sbNameLen == 0)
|
||||
continue;
|
||||
if (sb.name[sbNameLen - 1] == '/')
|
||||
{
|
||||
fs::create_directories(path, er);
|
||||
continue;
|
||||
}
|
||||
if(sb.size == 0)
|
||||
continue;
|
||||
if (sb.size > (1024 * 1024 * 128))
|
||||
continue; // skip unusually huge files
|
||||
|
||||
zip_file_t* zipFile = zip_fopen_index(za, i, 0);
|
||||
if (zipFile == nullptr)
|
||||
continue;
|
||||
|
||||
uint8* fileBuffer = new uint8[sb.size];
|
||||
|
||||
if (zip_fread(zipFile, fileBuffer, sb.size) == sb.size)
|
||||
{
|
||||
FileStream* fs = FileStream::createFile2(path);
|
||||
if (fs)
|
||||
{
|
||||
fs->writeData(fileBuffer, sb.size);
|
||||
delete fs;
|
||||
}
|
||||
}
|
||||
|
||||
delete [] fileBuffer;
|
||||
zip_fclose(zipFile);
|
||||
}
|
||||
|
||||
zip_error_fini(&error);
|
||||
createGraphicPackDownloadedVersionFile(assetName);
|
||||
m_threadState = ThreadFinished;
|
||||
}
|
||||
|
||||
DownloadGraphicPacksWindow::DownloadGraphicPacksWindow(wxWindow* parent)
|
||||
: wxDialog(parent, wxID_ANY, _("Checking version..."), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX),
|
||||
m_threadState(ThreadRunning), m_stage(StageCheckVersion), m_currentStage(StageCheckVersion)
|
||||
{
|
||||
auto* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_processBar = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxSize(500, 20), wxGA_HORIZONTAL);
|
||||
m_processBar->SetValue(0);
|
||||
m_processBar->SetRange(100);
|
||||
sizer->Add(m_processBar, 0, wxALL | wxEXPAND, 5);
|
||||
|
||||
auto* m_cancelButton = new wxButton(this, wxID_ANY, _("Cancel"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
m_cancelButton->Bind(wxEVT_BUTTON, &DownloadGraphicPacksWindow::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, &DownloadGraphicPacksWindow::OnUpdate, this);
|
||||
this->Bind(wxEVT_CLOSE_WINDOW, &DownloadGraphicPacksWindow::OnClose, this);
|
||||
m_timer->Start(250);
|
||||
|
||||
|
||||
m_downloadState = std::make_unique<curlDownloadFileState_t>();
|
||||
|
||||
m_thread = std::thread(&DownloadGraphicPacksWindow::UpdateThread, this);
|
||||
}
|
||||
|
||||
DownloadGraphicPacksWindow::~DownloadGraphicPacksWindow()
|
||||
{
|
||||
m_timer->Stop();
|
||||
if (m_thread.joinable())
|
||||
m_thread.join();
|
||||
}
|
||||
|
||||
const std::string& DownloadGraphicPacksWindow::GetException() const
|
||||
{
|
||||
return m_threadException;
|
||||
}
|
||||
|
||||
int DownloadGraphicPacksWindow::ShowModal()
|
||||
{
|
||||
wxDialog::ShowModal();
|
||||
return m_threadState == ThreadCanceled ? wxID_CANCEL : wxID_OK;
|
||||
}
|
||||
|
||||
void DownloadGraphicPacksWindow::OnClose(wxCloseEvent& event)
|
||||
{
|
||||
if (m_threadState == ThreadRunning)
|
||||
{
|
||||
//wxMessageDialog dialog(this, _("Do you really want to cancel the update process?\n\nCanceling the process will delete the applied update."), _("Info"), wxCENTRE | wxYES_NO);
|
||||
//if (dialog.ShowModal() != wxID_YES)
|
||||
// return;
|
||||
|
||||
m_threadState = ThreadCanceled;
|
||||
}
|
||||
|
||||
m_timer->Stop();
|
||||
if (m_thread.joinable())
|
||||
m_thread.join();
|
||||
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void DownloadGraphicPacksWindow::OnUpdate(const wxTimerEvent& event)
|
||||
{
|
||||
if (m_threadState != ThreadRunning)
|
||||
{
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
if (m_currentStage != m_stage)
|
||||
{
|
||||
if (m_stage == StageDownloading)
|
||||
{
|
||||
this->SetTitle(_("Downloading graphic packs..."));
|
||||
}
|
||||
else if (m_stage == StageExtracting)
|
||||
{
|
||||
this->SetTitle(_("Extracting..."));
|
||||
}
|
||||
m_currentStage = m_stage;
|
||||
}
|
||||
|
||||
if (m_currentStage == StageDownloading)
|
||||
{
|
||||
const sint32 processedSize = (sint32)(m_downloadState->progress * 100.0f);
|
||||
if (m_processBar->GetValue() != processedSize)
|
||||
m_processBar->SetValue(processedSize);
|
||||
}
|
||||
else if (m_currentStage == StageExtracting)
|
||||
{
|
||||
const sint32 processedSize = (sint32)(m_extractionProgress * 100.0f);
|
||||
if (m_processBar->GetValue() != processedSize)
|
||||
m_processBar->SetValue(processedSize);
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadGraphicPacksWindow::OnCancelButton(const wxCommandEvent& event)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
60
src/gui/DownloadGraphicPacksWindow.h
Normal file
60
src/gui/DownloadGraphicPacksWindow.h
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#include <wx/timer.h>
|
||||
#include <wx/gauge.h>
|
||||
|
||||
|
||||
class DownloadGraphicPacksWindow : public wxDialog
|
||||
{
|
||||
public:
|
||||
DownloadGraphicPacksWindow(wxWindow* parent);
|
||||
~DownloadGraphicPacksWindow();
|
||||
|
||||
const std::string& GetException() const;
|
||||
|
||||
int ShowModal() override;
|
||||
void OnClose(wxCloseEvent& event);
|
||||
|
||||
void OnUpdate(const wxTimerEvent& event);
|
||||
void OnCancelButton(const wxCommandEvent& event);
|
||||
|
||||
private:
|
||||
void UpdateThread();
|
||||
|
||||
enum ThreadState_t
|
||||
{
|
||||
ThreadRunning,
|
||||
ThreadCanceled,
|
||||
ThreadError,
|
||||
ThreadFinished,
|
||||
};
|
||||
|
||||
enum DownloadStage_t
|
||||
{
|
||||
StageCheckVersion,
|
||||
StageDownloading,
|
||||
StageExtracting
|
||||
};
|
||||
|
||||
std::atomic<ThreadState_t> m_threadState;
|
||||
std::atomic<DownloadStage_t> m_stage;
|
||||
std::atomic<double> m_extractionProgress;
|
||||
std::string m_threadException;
|
||||
std::thread m_thread;
|
||||
|
||||
DownloadStage_t m_currentStage;
|
||||
wxGauge* m_processBar;
|
||||
wxTimer* m_timer;
|
||||
|
||||
struct curlDownloadFileState_t;
|
||||
std::unique_ptr<curlDownloadFileState_t> m_downloadState;
|
||||
|
||||
static size_t curlDownloadFile_writeData(void* ptr, size_t size, size_t nmemb, curlDownloadFileState_t* downloadState);
|
||||
static int progress_callback(curlDownloadFileState_t* downloadState, double dltotal, double dlnow, double ultotal, double ulnow);
|
||||
static bool curlDownloadFile(const char* url, curlDownloadFileState_t* downloadState);
|
||||
};
|
||||
369
src/gui/GameProfileWindow.cpp
Normal file
369
src/gui/GameProfileWindow.cpp
Normal file
|
|
@ -0,0 +1,369 @@
|
|||
#include "gui/GameProfileWindow.h"
|
||||
|
||||
#include <wx/statbox.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/panel.h>
|
||||
#include <wx/notebook.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/wupdlock.h>
|
||||
#include <wx/slider.h>
|
||||
|
||||
#include "gui/helpers/wxHelpers.h"
|
||||
#include "input/InputManager.h"
|
||||
|
||||
#if BOOST_OS_LINUX
|
||||
#include "resource/linux/resources.h"
|
||||
#endif
|
||||
|
||||
GameProfileWindow::GameProfileWindow(wxWindow* parent, uint64_t title_id)
|
||||
: wxFrame(parent, wxID_ANY, _("Edit game profile"), wxDefaultPosition, wxSize{ 390, 350 }, wxCLOSE_BOX | wxCLIP_CHILDREN | wxCAPTION | wxRESIZE_BORDER | wxTAB_TRAVERSAL | wxSYSTEM_MENU), m_title_id(title_id)
|
||||
{
|
||||
SetIcon(wxICON(X_GAME_PROFILE));
|
||||
|
||||
m_game_profile.Reset();
|
||||
m_game_profile.Load(title_id, false);
|
||||
|
||||
this->SetSizeHints(wxDefaultSize, wxDefaultSize);
|
||||
|
||||
auto* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto* m_notebook = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0);
|
||||
// general
|
||||
{
|
||||
auto* panel = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
auto* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
{
|
||||
auto* box_sizer = new wxStaticBoxSizer(new wxStaticBox(panel, wxID_ANY, _("General")), wxVERTICAL);
|
||||
auto* box = box_sizer->GetStaticBox();
|
||||
|
||||
m_load_libs = new wxCheckBox(box, wxID_ANY, _("Load shared libraries"));
|
||||
m_load_libs->SetToolTip(_("EXPERT OPTION\nThis option will load libraries from the cafeLibs directory"));
|
||||
box_sizer->Add(m_load_libs, 0, wxALL, 5);
|
||||
|
||||
m_start_with_padview = new wxCheckBox(box, wxID_ANY, _("Launch with gamepad view"));
|
||||
m_start_with_padview->SetToolTip(_("Games will be launched with gamepad view toggled as default. The view can be toggled with CTRL + TAB"));
|
||||
box_sizer->Add(m_start_with_padview, 0, wxALL, 5);
|
||||
|
||||
sizer->Add(box_sizer, 0, wxEXPAND, 5);
|
||||
}
|
||||
// cpu
|
||||
{
|
||||
auto* box_sizer = new wxStaticBoxSizer(new wxStaticBox(panel, wxID_ANY, _("CPU")), wxVERTICAL);
|
||||
auto* box = box_sizer->GetStaticBox();
|
||||
|
||||
auto* first_row = new wxFlexGridSizer(0, 2, 0, 0);
|
||||
first_row->SetFlexibleDirection(wxBOTH);
|
||||
first_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
||||
|
||||
first_row->Add(new wxStaticText(box, wxID_ANY, _("Mode")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
|
||||
wxString cpu_modes[] = { _("Single-core interpreter"), _("Single-core recompiler"), _("Multi-core recompiler"), _("Auto (recommended)") };
|
||||
const sint32 m_cpu_modeNChoices = std::size(cpu_modes);
|
||||
m_cpu_mode = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_cpu_modeNChoices, cpu_modes, 0);
|
||||
m_cpu_mode->SetToolTip(_("Set the CPU emulation mode"));
|
||||
first_row->Add(m_cpu_mode, 0, wxALL, 5);
|
||||
|
||||
first_row->Add(new wxStaticText(box, wxID_ANY, _("Thread quantum")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
|
||||
auto* quantum_sizer = new wxFlexGridSizer(0, 2, 0, 0);
|
||||
quantum_sizer->SetFlexibleDirection(wxBOTH);
|
||||
quantum_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
||||
|
||||
wxString quantum_values[] = { "20000", "45000", "60000", "80000" ,"100000" };
|
||||
m_thread_quantum = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(quantum_values), quantum_values);
|
||||
m_thread_quantum->SetMinSize(wxSize(85, -1));
|
||||
m_thread_quantum->SetToolTip(_("EXPERT OPTION\nSet the maximum thread slice runtime (in virtual cycles)"));
|
||||
quantum_sizer->Add(m_thread_quantum, 0, wxALL, 5);
|
||||
|
||||
quantum_sizer->Add(new wxStaticText(box, wxID_ANY, _("cycles")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
|
||||
first_row->Add(quantum_sizer, 0, wxEXPAND, 5);
|
||||
|
||||
box_sizer->Add(first_row, 0, wxEXPAND, 5);
|
||||
|
||||
|
||||
sizer->Add(box_sizer, 0, wxEXPAND, 5);
|
||||
}
|
||||
|
||||
panel->SetSizer(sizer);
|
||||
panel->Layout();
|
||||
sizer->Fit(panel);
|
||||
m_notebook->AddPage(panel, _("General"), true);
|
||||
}
|
||||
|
||||
// graphic
|
||||
{
|
||||
auto* panel = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
auto* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
//m_extended_texture_readback = new wxCheckBox(panel, wxID_ANY, _("Extended texture readback"));
|
||||
//m_extended_texture_readback->SetToolTip(_("Improves emulation accuracy of CPU to GPU memory access at the cost of performance. Required for some games."));
|
||||
//sizer->Add(m_extended_texture_readback, 0, wxALL, 5);
|
||||
|
||||
auto* first_row = new wxFlexGridSizer(0, 2, 0, 0);
|
||||
first_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
||||
|
||||
/*first_row->Add(new wxStaticText(panel, wxID_ANY, _("Precompiled shaders")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
wxString precompiled_modes[] = { _("auto"), _("enable"), _("disable") };
|
||||
const sint32 precompiled_count = std::size(precompiled_modes);
|
||||
m_precompiled = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, precompiled_count, precompiled_modes, 0);
|
||||
m_precompiled->SetToolTip(_("Precompiled shaders can speed up the load time on the shader loading screen.\nAuto will enable it for AMD/Intel but disable it for NVIDIA GPUs as a workaround for a driver bug.\n\nRecommended: Auto"));
|
||||
first_row->Add(m_precompiled, 0, wxALL, 5);*/
|
||||
|
||||
first_row->Add(new wxStaticText(panel, wxID_ANY, _("Graphics API")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
|
||||
wxString gapi_values[] = { "", "OpenGL", "Vulkan" };
|
||||
m_graphic_api = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, (int)std::size(gapi_values), gapi_values);
|
||||
first_row->Add(m_graphic_api, 0, wxALL, 5);
|
||||
|
||||
first_row->Add(new wxStaticText(panel, wxID_ANY, _("Shader mul accuracy")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
|
||||
wxString mul_values[] = { _("false"), _("true"), _("minimal") };
|
||||
m_shader_mul_accuracy = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, (int)std::size(mul_values), mul_values);
|
||||
m_shader_mul_accuracy->SetToolTip(_("EXPERT OPTION\nControls the accuracy of floating point multiplication in shaders.\n\nRecommended: true"));
|
||||
first_row->Add(m_shader_mul_accuracy, 0, wxALL, 5);
|
||||
|
||||
/*first_row->Add(new wxStaticText(panel, wxID_ANY, _("GPU buffer cache accuracy")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
wxString accuarcy_values[] = { _("high"), _("medium"), _("low") };
|
||||
m_cache_accuracy = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, (int)std::size(accuarcy_values), accuarcy_values);
|
||||
m_cache_accuracy->SetToolTip(_("Lower value results in higher performance, but may cause graphical issues"));
|
||||
first_row->Add(m_cache_accuracy, 0, wxALL, 5);*/
|
||||
|
||||
sizer->Add(first_row, 0, wxEXPAND, 5);
|
||||
|
||||
|
||||
panel->SetSizer(sizer);
|
||||
panel->Layout();
|
||||
sizer->Fit(panel);
|
||||
m_notebook->AddPage(panel, _("Graphic"), false);
|
||||
}
|
||||
|
||||
//// audio
|
||||
//{
|
||||
// auto panel = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
// wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
// m_disable_audio = new wxCheckBox(panel, wxID_ANY, _("Disable Audio"), wxDefaultPosition, wxDefaultSize, wxCHK_3STATE | wxCHK_ALLOW_3RD_STATE_FOR_USER);
|
||||
// sizer->Add(m_disable_audio, 0, wxALL, 5);
|
||||
|
||||
|
||||
// panel->SetSizer(sizer);
|
||||
// panel->Layout();
|
||||
// sizer->Fit(panel);
|
||||
// m_notebook->AddPage(panel, _("Audio"), false);
|
||||
//}
|
||||
|
||||
// controller
|
||||
{
|
||||
auto panel = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
wxBoxSizer* sizer;
|
||||
sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
wxFlexGridSizer* profile_sizer;
|
||||
profile_sizer = new wxFlexGridSizer(0, 2, 0, 0);
|
||||
profile_sizer->SetFlexibleDirection(wxBOTH);
|
||||
profile_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
||||
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
profile_sizer->Add(new wxStaticText(panel, wxID_ANY, fmt::format("{} {}", _("Controller").ToStdString(), (i + 1))), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
|
||||
wxArrayString m_controller_profileChoices;
|
||||
m_controller_profile[i] = new wxComboBox(panel, wxID_ANY,"", wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_DROPDOWN| wxCB_READONLY);
|
||||
m_controller_profile[i]->SetMinSize(wxSize(250, -1));
|
||||
m_controller_profile[i]->Bind(wxEVT_COMBOBOX_DROPDOWN, &GameProfileWindow::OnControllerProfileDropdown, this);
|
||||
m_controller_profile[i]->SetToolTip(_("Forces a given controller profile"));
|
||||
profile_sizer->Add(m_controller_profile[i], 0, wxALL, 5);
|
||||
}
|
||||
|
||||
sizer->Add(profile_sizer, 0, wxEXPAND, 5);
|
||||
|
||||
panel->SetSizer(sizer);
|
||||
panel->Layout();
|
||||
sizer->Fit(panel);
|
||||
m_notebook->AddPage(panel, _("Controller"), false);
|
||||
}
|
||||
|
||||
main_sizer->Add(m_notebook, 1, wxEXPAND | wxALL, 5);
|
||||
|
||||
|
||||
this->SetSizer(main_sizer);
|
||||
this->Layout();
|
||||
|
||||
this->Centre(wxBOTH);
|
||||
|
||||
ApplyProfile();
|
||||
}
|
||||
|
||||
GameProfileWindow::~GameProfileWindow()
|
||||
{
|
||||
SaveProfile();
|
||||
}
|
||||
|
||||
void GameProfileWindow::OnStreamoutSizeChange(wxCommandEvent& event)
|
||||
{
|
||||
wxSlider* slider = wxDynamicCast(event.GetEventObject(), wxSlider);
|
||||
wxASSERT(slider);
|
||||
wxStaticText* text = wxDynamicCast(slider->GetClientData(), wxStaticText);
|
||||
wxASSERT(text);
|
||||
text->SetLabelText(fmt::format("{} MB", slider->GetValue()));
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void GameProfileWindow::OnControllerProfileDropdown(wxCommandEvent& event)
|
||||
{
|
||||
wxComboBox* cb = wxDynamicCast(event.GetEventObject(), wxComboBox);
|
||||
wxASSERT(cb);
|
||||
|
||||
wxWindowUpdateLocker lock(cb);
|
||||
|
||||
const auto selected_value = cb->GetStringSelection();
|
||||
cb->Clear();
|
||||
cb->Append(wxEmptyString);
|
||||
|
||||
auto profiles = InputManager::get_profiles();
|
||||
for (const auto& profile : profiles)
|
||||
{
|
||||
cb->Append(to_wxString(profile));
|
||||
}
|
||||
|
||||
cb->SetStringSelection(selected_value);
|
||||
}
|
||||
|
||||
void GameProfileWindow::SetProfileInt(gameProfileIntegerOption_t& option, wxCheckBox* checkbox, sint32 value) const
|
||||
{
|
||||
const auto state = checkbox->GetValue();
|
||||
if (state)
|
||||
{
|
||||
option.isPresent = true;
|
||||
option.value = value;
|
||||
}
|
||||
else
|
||||
option.isPresent = false;
|
||||
}
|
||||
|
||||
void GameProfileWindow::ApplyProfile()
|
||||
{
|
||||
if(m_game_profile.m_gameName)
|
||||
this->SetTitle(fmt::format("{} - {}", _("Edit game profile").ToStdString(), m_game_profile.m_gameName.value()));
|
||||
|
||||
// general
|
||||
m_load_libs->SetValue(m_game_profile.m_loadSharedLibraries.value());
|
||||
m_start_with_padview->SetValue(m_game_profile.m_startWithPadView);
|
||||
|
||||
// cpu
|
||||
// wxString cpu_modes[] = { _("Singlecore-Interpreter"), _("Singlecore-Recompiler"), _("Triplecore-Recompiler"), _("Auto (recommended)") };
|
||||
switch(m_game_profile.m_cpuMode.value())
|
||||
{
|
||||
case CPUMode::SinglecoreInterpreter: m_cpu_mode->SetSelection(0); break;
|
||||
case CPUMode::SinglecoreRecompiler: m_cpu_mode->SetSelection(1); break;
|
||||
case CPUMode::DualcoreRecompiler: m_cpu_mode->SetSelection(2); break;
|
||||
case CPUMode::MulticoreRecompiler: m_cpu_mode->SetSelection(2); break;
|
||||
default: m_cpu_mode->SetSelection(3);
|
||||
}
|
||||
|
||||
m_thread_quantum->SetStringSelection(fmt::format("{}", m_game_profile.m_threadQuantum));
|
||||
|
||||
// gpu
|
||||
if (!m_game_profile.m_graphics_api.has_value())
|
||||
m_graphic_api->SetSelection(0); // selecting ""
|
||||
else
|
||||
m_graphic_api->SetSelection(1 + m_game_profile.m_graphics_api.value()); // "", OpenGL, Vulkan
|
||||
//m_extended_texture_readback->SetValue(m_game_profile.m_extendedTextureReadback);
|
||||
//m_precompiled->SetSelection((int)m_game_profile.m_precompiledShaders.value());
|
||||
m_shader_mul_accuracy->SetSelection((int)m_game_profile.m_accurateShaderMul);
|
||||
//m_cache_accuracy->SetSelection((int)m_game_profile.m_gpuBufferCacheAccuracy.value());
|
||||
|
||||
//// audio
|
||||
//m_disable_audio->Set3StateValue(GetCheckboxState(m_game_profile.disableAudio));
|
||||
|
||||
// controller
|
||||
auto profiles = InputManager::get_profiles();
|
||||
|
||||
for (const auto& cb : m_controller_profile)
|
||||
{
|
||||
cb->Clear();
|
||||
for (const auto& profile : profiles)
|
||||
{
|
||||
cb->Append(to_wxString(profile));
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < InputManager::kMaxController; ++i)
|
||||
{
|
||||
const bool has_value = m_game_profile.m_controllerProfile[i].has_value();
|
||||
if (has_value)
|
||||
{
|
||||
const auto& v = m_game_profile.m_controllerProfile[i].value();
|
||||
m_controller_profile[i]->SetStringSelection(wxString::FromUTF8(v));
|
||||
}
|
||||
|
||||
else
|
||||
m_controller_profile[i]->SetSelection(wxNOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
void GameProfileWindow::SaveProfile()
|
||||
{
|
||||
// update game profile struct
|
||||
m_game_profile.Reset();
|
||||
// general
|
||||
m_game_profile.m_loadSharedLibraries = m_load_libs->GetValue();
|
||||
m_game_profile.m_startWithPadView = m_start_with_padview->GetValue();
|
||||
|
||||
// cpu
|
||||
switch(m_cpu_mode->GetSelection())
|
||||
{
|
||||
case 0: m_game_profile.m_cpuMode = CPUMode::SinglecoreInterpreter; break;
|
||||
case 1: m_game_profile.m_cpuMode = CPUMode::SinglecoreRecompiler; break;
|
||||
case 2: m_game_profile.m_cpuMode = CPUMode::MulticoreRecompiler; break;
|
||||
default:
|
||||
m_game_profile.m_cpuMode = CPUMode::Auto;
|
||||
}
|
||||
|
||||
|
||||
const wxString thread_quantum = m_thread_quantum->GetStringSelection();
|
||||
if (!thread_quantum.empty())
|
||||
{
|
||||
m_game_profile.m_threadQuantum = ConvertString<uint32>(thread_quantum.ToStdString());
|
||||
m_game_profile.m_threadQuantum = std::min<uint32>(m_game_profile.m_threadQuantum, 536870912);
|
||||
m_game_profile.m_threadQuantum = std::max<uint32>(m_game_profile.m_threadQuantum, 5000);
|
||||
}
|
||||
|
||||
// gpu
|
||||
m_game_profile.m_accurateShaderMul = (AccurateShaderMulOption)m_shader_mul_accuracy->GetSelection();
|
||||
if (m_graphic_api->GetSelection() == 0)
|
||||
m_game_profile.m_graphics_api = {};
|
||||
else
|
||||
m_game_profile.m_graphics_api = (GraphicAPI)(m_graphic_api->GetSelection() - 1); // "", OpenGL, Vulkan
|
||||
|
||||
// controller
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
if(m_controller_profile[i]->GetSelection() == wxNOT_FOUND)
|
||||
{
|
||||
m_game_profile.m_controllerProfile[i].reset();
|
||||
continue;
|
||||
}
|
||||
|
||||
const wxString profile_name = m_controller_profile[i]->GetStringSelection();
|
||||
if (profile_name.empty())
|
||||
m_game_profile.m_controllerProfile[i].reset();
|
||||
else
|
||||
m_game_profile.m_controllerProfile[i] = profile_name.ToUTF8();
|
||||
}
|
||||
|
||||
// update game profile file
|
||||
m_game_profile.Save(m_title_id);
|
||||
}
|
||||
|
||||
void GameProfileWindow::SetSliderValue(wxSlider* slider, sint32 new_value) const
|
||||
{
|
||||
wxASSERT(slider);
|
||||
slider->SetValue(new_value);
|
||||
|
||||
wxCommandEvent slider_event(wxEVT_SLIDER, slider->GetId());
|
||||
slider_event.SetEventObject(slider);
|
||||
slider_event.SetClientData((void*)IsFrozen());
|
||||
wxPostEvent(slider->GetEventHandler(), slider_event);
|
||||
}
|
||||
50
src/gui/GameProfileWindow.h
Normal file
50
src/gui/GameProfileWindow.h
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#pragma once
|
||||
|
||||
#include <wx/frame.h>
|
||||
#include <wx/checkbox.h>
|
||||
#include <wx/choice.h>
|
||||
#include <wx/combobox.h>
|
||||
#include <wx/slider.h>
|
||||
|
||||
#include "Cafe/GameProfile/GameProfile.h"
|
||||
|
||||
class GameProfileWindow : public wxFrame
|
||||
{
|
||||
public:
|
||||
GameProfileWindow(wxWindow* parent, uint64_t title_id);
|
||||
~GameProfileWindow();
|
||||
|
||||
private:
|
||||
uint64_t m_title_id;
|
||||
GameProfile m_game_profile;
|
||||
|
||||
void OnStreamoutSizeChange(wxCommandEvent& event);
|
||||
void OnControllerProfileDropdown(wxCommandEvent& event);
|
||||
|
||||
void SetSliderValue(wxSlider* slider, sint32 new_value) const;
|
||||
void SetProfileInt(gameProfileIntegerOption_t& option, wxCheckBox* checkbox, sint32 value) const;
|
||||
|
||||
void ApplyProfile();
|
||||
void SaveProfile();
|
||||
|
||||
// general
|
||||
wxCheckBox* m_load_libs, *m_start_with_padview;
|
||||
|
||||
// cpu
|
||||
wxChoice *m_cpu_mode;
|
||||
wxChoice* m_thread_quantum;
|
||||
|
||||
// gpu
|
||||
//wxCheckBox* m_extended_texture_readback;
|
||||
//wxChoice* m_precompiled;
|
||||
wxChoice* m_graphic_api;
|
||||
|
||||
wxChoice* m_shader_mul_accuracy;
|
||||
//wxChoice* m_cache_accuracy;
|
||||
|
||||
// audio
|
||||
//wxCheckBox* m_disable_audio;
|
||||
|
||||
// controller
|
||||
wxComboBox* m_controller_profile[8];
|
||||
};
|
||||
350
src/gui/GameUpdateWindow.cpp
Normal file
350
src/gui/GameUpdateWindow.cpp
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
#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(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(target_location.root_path());
|
||||
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);
|
||||
}
|
||||
|
||||
const auto target_path = fs::path(m_target_path);
|
||||
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(), _utf8Wrapper(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();
|
||||
}
|
||||
67
src/gui/GameUpdateWindow.h
Normal file
67
src/gui/GameUpdateWindow.h
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#pragma once
|
||||
|
||||
#include "Cafe/TitleList/GameInfo.h"
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/timer.h>
|
||||
#include <wx/gauge.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
// thrown if users doesn't wish to reinstall update/dlc
|
||||
class AbortException : public std::exception {};
|
||||
|
||||
class GameUpdateWindow : public wxDialog
|
||||
{
|
||||
public:
|
||||
|
||||
GameUpdateWindow(wxWindow& parent, const fs::path& metaPath);
|
||||
~GameUpdateWindow();
|
||||
|
||||
uint64 GetTitleId() const { return m_title_info.GetAppTitleId(); }
|
||||
bool HasException() const { return !m_thread_exception.empty(); }
|
||||
//bool IsDLC() const { return m_game_info->IsDLC(); }
|
||||
//bool IsUpdate() const { return m_game_info->IsUpdate(); }
|
||||
const std::string& GetExceptionMessage() const { return m_thread_exception; }
|
||||
const std::string GetGameName() const { return m_title_info.GetTitleName(); }
|
||||
uint32 GetTargetVersion() const { return m_title_info.GetAppTitleVersion(); }
|
||||
fs::path GetTargetPath() const { return fs::path(m_target_path); }
|
||||
|
||||
int ShowModal() override;
|
||||
void OnClose(wxCloseEvent& event);
|
||||
|
||||
void OnUpdate(const wxTimerEvent& event);
|
||||
void OnCancelButton(const wxCommandEvent& event);
|
||||
|
||||
//uint64 GetUpdateTitleId() const { return m_title_info->GetUpdateTitleId(); }
|
||||
//uint64 GetDLCTitleId() const { return m_game_info->GetDLCTitleId(); }
|
||||
|
||||
private:
|
||||
//std::unique_ptr<GameInfoDEPRECATED> m_game_info;
|
||||
TitleInfo m_title_info;
|
||||
enum ThreadState_t
|
||||
{
|
||||
ThreadRunning,
|
||||
ThreadCanceled,
|
||||
ThreadFinished,
|
||||
};
|
||||
|
||||
uint64_t m_required_size;
|
||||
fs::path m_target_path;
|
||||
std::array<fs::path, 3> m_source_paths;
|
||||
bool ParseUpdate(const fs::path& metaPath);
|
||||
|
||||
std::atomic<uint64> m_processed_size = 0;
|
||||
std::atomic<ThreadState_t> m_thread_state;
|
||||
std::string m_thread_exception;
|
||||
std::thread m_thread;
|
||||
void ThreadWork();
|
||||
|
||||
fs::path m_backup_folder; // for prev update data
|
||||
|
||||
wxGauge* m_processBar;
|
||||
wxTimer* m_timer;
|
||||
};
|
||||
1802
src/gui/GeneralSettings2.cpp
Normal file
1802
src/gui/GeneralSettings2.cpp
Normal file
File diff suppressed because it is too large
Load diff
108
src/gui/GeneralSettings2.h
Normal file
108
src/gui/GeneralSettings2.h
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
#pragma once
|
||||
#include <wx/collpane.h>
|
||||
#include <wx/propgrid/propgrid.h>
|
||||
|
||||
class wxColourPickerCtrl;
|
||||
|
||||
wxDECLARE_EVENT(wxEVT_ACCOUNTLIST_REFRESH, wxCommandEvent);
|
||||
|
||||
class GeneralSettings2 : public wxDialog
|
||||
{
|
||||
public:
|
||||
GeneralSettings2(wxWindow* parent, bool game_launched);
|
||||
~GeneralSettings2();
|
||||
|
||||
[[nodiscard]] bool ShouldReloadGamelist() const { return m_reload_gamelist; }
|
||||
[[nodiscard]] bool MLCModified() const { return m_mlc_modified; }
|
||||
void OnClose(wxCloseEvent& event);
|
||||
|
||||
private:
|
||||
void ValidateConfig();
|
||||
void StoreConfig();
|
||||
void DisableSettings(bool game_launched);
|
||||
|
||||
bool m_reload_gamelist = false;
|
||||
bool m_mlc_modified = false;
|
||||
bool m_game_launched;
|
||||
|
||||
bool m_has_account_change = false; // keep track of dirty state of accounts
|
||||
|
||||
|
||||
wxPanel* AddGeneralPage(wxNotebook* notebook);
|
||||
wxPanel* AddGraphicsPage(wxNotebook* notebook);
|
||||
wxPanel* AddAudioPage(wxNotebook* notebook);
|
||||
wxPanel* AddOverlayPage(wxNotebook* notebook);
|
||||
wxPanel* AddAccountPage(wxNotebook* notebook);
|
||||
wxPanel* AddDebugPage(wxNotebook* notebook);
|
||||
|
||||
// General
|
||||
wxChoice * m_language;
|
||||
wxCheckBox* m_save_window_position_size;
|
||||
wxCheckBox* m_save_padwindow_position_size;
|
||||
wxCheckBox* m_discord_presence, *m_fullscreen_menubar;
|
||||
wxCheckBox* m_auto_update, *m_save_screenshot;
|
||||
wxCheckBox* m_permanent_storage;
|
||||
wxListBox* m_game_paths;
|
||||
wxTextCtrl* m_mlc_path;
|
||||
|
||||
// Graphics
|
||||
wxChoice* m_graphic_api, * m_graphic_device;
|
||||
wxChoice* m_vsync;
|
||||
wxCheckBox *m_async_compile, *m_gx2drawdone_sync;
|
||||
wxRadioBox* m_upscale_filter, *m_downscale_filter, *m_fullscreen_scaling;
|
||||
wxChoice* m_overlay_position, *m_notification_position, *m_overlay_scale, *m_notification_scale;
|
||||
wxCheckBox* m_controller_profile_name, *m_controller_low_battery, *m_shader_compiling, *m_friends_data;
|
||||
wxCheckBox *m_overlay_fps, *m_overlay_drawcalls, *m_overlay_cpu, *m_overlay_cpu_per_core,*m_overlay_ram, *m_overlay_vram, *m_overlay_debug;
|
||||
wxColourPickerCtrl *m_overlay_font_color, *m_notification_font_color;
|
||||
|
||||
// Audio
|
||||
wxChoice* m_audio_api;
|
||||
wxSlider *m_audio_latency;
|
||||
wxSlider *m_tv_volume, *m_pad_volume;
|
||||
wxChoice *m_tv_channels, *m_pad_channels;
|
||||
wxChoice *m_tv_device, *m_pad_device;
|
||||
|
||||
// Account
|
||||
wxButton* m_create_account, * m_delete_account;
|
||||
wxChoice* m_active_account;
|
||||
wxCheckBox* m_online_enabled;
|
||||
wxCollapsiblePane* m_account_information;
|
||||
wxPropertyGrid* m_account_grid;
|
||||
wxBitmapButton* m_validate_online;
|
||||
wxStaticText* m_online_status;
|
||||
|
||||
// Debug
|
||||
wxChoice* m_crash_dump;
|
||||
|
||||
void OnAccountCreate(wxCommandEvent& event);
|
||||
void OnAccountDelete(wxCommandEvent& event);
|
||||
void OnAccountSettingsChanged(wxPropertyGridEvent& event);
|
||||
void OnAudioLatencyChanged(wxCommandEvent& event);
|
||||
void OnVolumeChanged(wxCommandEvent& event);
|
||||
void OnInputVolumeChanged(wxCommandEvent& event);
|
||||
void OnSliderChangedPercent(wxCommandEvent& event);
|
||||
void OnLatencySliderChanged(wxCommandEvent& event);
|
||||
void OnAudioAPISelected(wxCommandEvent& event);
|
||||
void OnAudioDeviceSelected(wxCommandEvent& event);
|
||||
void OnAudioChannelsSelected(wxCommandEvent& event);
|
||||
void OnGraphicAPISelected(wxCommandEvent& event);
|
||||
void OnAddPathClicked(wxCommandEvent& event);
|
||||
void OnRemovePathClicked(wxCommandEvent& event);
|
||||
void OnActiveAccountChanged(wxCommandEvent& event);
|
||||
void OnMLCPathSelect(wxCommandEvent& event);
|
||||
void OnMLCPathChar(wxKeyEvent& event);
|
||||
void OnShowOnlineValidator(wxCommandEvent& event);
|
||||
void OnOnlineEnable(wxCommandEvent& event);
|
||||
|
||||
// updates cemu audio devices
|
||||
void UpdateAudioDevice();
|
||||
// refreshes audio device list for dropdown
|
||||
void UpdateAudioDeviceList();
|
||||
|
||||
void ResetAccountInformation();
|
||||
void UpdateAccountInformation();
|
||||
void UpdateOnlineAccounts();
|
||||
void HandleGraphicsApiSelection();
|
||||
void ApplyConfig();
|
||||
};
|
||||
|
||||
314
src/gui/GettingStartedDialog.cpp
Normal file
314
src/gui/GettingStartedDialog.cpp
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
#include "gui/GettingStartedDialog.h"
|
||||
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/filepicker.h>
|
||||
#include <wx/statbox.h>
|
||||
#include <wx/msgdlg.h>
|
||||
|
||||
#include "config/ActiveSettings.h"
|
||||
#include "gui/CemuApp.h"
|
||||
#include "gui/DownloadGraphicPacksWindow.h"
|
||||
#include "gui/GraphicPacksWindow2.h"
|
||||
#include "gui/input/InputSettings2.h"
|
||||
#include "config/CemuConfig.h"
|
||||
#include "config/PermanentConfig.h"
|
||||
|
||||
#include "Cafe/TitleList/TitleList.h"
|
||||
|
||||
#if BOOST_OS_LINUX > 0
|
||||
#include "resource/linux/resources.h"
|
||||
#endif
|
||||
|
||||
#include "wxHelper.h"
|
||||
|
||||
wxPanel* GettingStartedDialog::CreatePage1()
|
||||
{
|
||||
auto* result = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
auto* page1_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
{
|
||||
auto* sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
sizer->Add(new wxStaticBitmap(result, wxID_ANY, wxICON(M_WND_ICON128)), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
|
||||
auto* m_staticText11 = new wxStaticText(result, wxID_ANY, _("It looks like you're starting Cemu for the first time.\nThis quick setup assistant will help you get the best experience"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
m_staticText11->Wrap(-1);
|
||||
sizer->Add(m_staticText11, 0, wxALL, 5);
|
||||
|
||||
page1_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
|
||||
{
|
||||
m_mlc_box_sizer = new wxStaticBoxSizer(wxVERTICAL, result, _("mlc01 path"));
|
||||
m_mlc_box_sizer->Add(new wxStaticText(m_mlc_box_sizer->GetStaticBox(), wxID_ANY, _("The mlc path is the root folder of the emulated Wii U internal flash storage. It contains all your saves, installed updates and DLCs.\nIt is strongly recommend that you create a dedicated folder for it (example: C:\\wiiu\\mlc\\) \nIf left empty, the mlc folder will be created inside the Cemu folder.")), 0, wxALL, 5);
|
||||
|
||||
m_prev_mlc_warning = new wxStaticText(m_mlc_box_sizer->GetStaticBox(), wxID_ANY, _("A custom mlc path from a previous Cemu installation has been found and filled in."));
|
||||
m_prev_mlc_warning->SetForegroundColour(*wxRED);
|
||||
m_prev_mlc_warning->Show(false);
|
||||
m_mlc_box_sizer->Add(m_prev_mlc_warning, 0, wxALL, 5);
|
||||
|
||||
auto* mlc_path_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
mlc_path_sizer->Add(new wxStaticText(m_mlc_box_sizer->GetStaticBox(), wxID_ANY, _("Custom mlc01 path")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
|
||||
// workaround since we can't specify our own browse label? >> _("Browse")
|
||||
m_mlc_folder = new wxDirPickerCtrl(m_mlc_box_sizer->GetStaticBox(), wxID_ANY, wxEmptyString, _("Select a folder"), wxDefaultPosition, wxDefaultSize, wxDIRP_DEFAULT_STYLE);
|
||||
auto tTest1 = m_mlc_folder->GetTextCtrl();
|
||||
if(m_mlc_folder->HasTextCtrl())
|
||||
{
|
||||
m_mlc_folder->GetTextCtrl()->SetEditable(false);
|
||||
m_mlc_folder->GetTextCtrl()->Bind(wxEVT_CHAR, &GettingStartedDialog::OnMLCPathChar, this);
|
||||
}
|
||||
mlc_path_sizer->Add(m_mlc_folder, 1, wxALL, 5);
|
||||
|
||||
mlc_path_sizer->Add(new wxStaticText(m_mlc_box_sizer->GetStaticBox(), wxID_ANY, _("(optional)")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
|
||||
m_mlc_box_sizer->Add(mlc_path_sizer, 0, wxEXPAND, 5);
|
||||
|
||||
page1_sizer->Add(m_mlc_box_sizer, 0, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
|
||||
{
|
||||
auto* sizer = new wxStaticBoxSizer(wxVERTICAL, result, _("Game paths"));
|
||||
|
||||
sizer->Add(new wxStaticText(sizer->GetStaticBox(), wxID_ANY, _("The game path is scanned by Cemu to locate your games. We recommend creating a dedicated directory in which\nyou place all your Wii U games. (example: C:\\wiiu\\games\\)\n\nYou can also set additional paths in the general settings of Cemu.")), 0, wxALL, 5);
|
||||
|
||||
auto* game_path_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
game_path_sizer->Add(new wxStaticText(sizer->GetStaticBox(), wxID_ANY, _("Game path")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
|
||||
m_game_path = new wxDirPickerCtrl(sizer->GetStaticBox(), wxID_ANY, wxEmptyString, _("Select a folder"));
|
||||
game_path_sizer->Add(m_game_path, 1, wxALL, 5);
|
||||
|
||||
sizer->Add(game_path_sizer, 0, wxEXPAND, 5);
|
||||
|
||||
page1_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
|
||||
{
|
||||
auto* sizer = new wxStaticBoxSizer(wxVERTICAL, result, _("Graphic packs"));
|
||||
|
||||
sizer->Add(new wxStaticText(sizer->GetStaticBox(), wxID_ANY, _("Graphic packs improve games by offering the possibility to change resolution, tweak FPS or add other visual or gameplay modifications.\nDownload the community graphic packs to get started.\n")), 0, wxALL, 5);
|
||||
|
||||
auto* download_gp = new wxButton(sizer->GetStaticBox(), wxID_ANY, _("Download community graphic packs"));
|
||||
download_gp->Bind(wxEVT_BUTTON, &GettingStartedDialog::OnDownloadGPs, this);
|
||||
sizer->Add(download_gp, 0, wxALIGN_CENTER | wxALL, 5);
|
||||
|
||||
page1_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
|
||||
{
|
||||
auto* sizer = new wxFlexGridSizer(0, 1, 0, 0);
|
||||
sizer->AddGrowableCol(0);
|
||||
sizer->AddGrowableRow(0);
|
||||
sizer->SetFlexibleDirection(wxBOTH);
|
||||
sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_ALL);
|
||||
|
||||
auto* next = new wxButton(result, wxID_ANY, _("Next"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
next->Bind(wxEVT_BUTTON, [this](const auto&){m_notebook->SetSelection(1); });
|
||||
sizer->Add(next, 0, wxALIGN_BOTTOM | wxALIGN_RIGHT | wxALL, 5);
|
||||
|
||||
page1_sizer->Add(sizer, 1, wxEXPAND, 5);
|
||||
}
|
||||
|
||||
|
||||
result->SetSizer(page1_sizer);
|
||||
return result;
|
||||
}
|
||||
|
||||
wxPanel* GettingStartedDialog::CreatePage2()
|
||||
{
|
||||
auto* result = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
auto* page2_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
{
|
||||
auto* sizer = new wxStaticBoxSizer(new wxStaticBox(result, wxID_ANY, _("Input settings")), wxVERTICAL);
|
||||
|
||||
sizer->Add(new wxStaticText(sizer->GetStaticBox(), wxID_ANY, _("You can configure one controller for each player.\nWe advise you to always use GamePad as emulated input for the first player, since many games expect the GamePad to be present.\nIt is also required for touch functionality.\nThe default global hotkeys are:\nCTRL - show pad screen\nCTRL + TAB - toggle pad screen\nALT + ENTER - toggle fullscreen\nESC - leave fullscreen\n\nIf you're having trouble configuring your controller, make sure to have it in idle state and press calibrate.\nAlso don't set the axis deadzone too low.")), 0, wxALL, 5);
|
||||
|
||||
auto* input_button = new wxButton(sizer->GetStaticBox(), wxID_ANY, _("Configure input"));
|
||||
input_button->Bind(wxEVT_BUTTON, &GettingStartedDialog::OnInputSettings, this);
|
||||
sizer->Add(input_button, 0, wxALIGN_CENTER | wxALL, 5);
|
||||
|
||||
page2_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
|
||||
{
|
||||
auto* sizer = new wxStaticBoxSizer(new wxStaticBox(result, wxID_ANY, _("Additional options")), wxVERTICAL);
|
||||
|
||||
auto* option_sizer = new wxFlexGridSizer(0, 2, 0, 0);
|
||||
option_sizer->SetFlexibleDirection(wxBOTH);
|
||||
option_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
||||
|
||||
m_fullscreen = new wxCheckBox(sizer->GetStaticBox(), wxID_ANY, _("Start games with fullscreen"));
|
||||
option_sizer->Add(m_fullscreen, 0, wxALL, 5);
|
||||
|
||||
m_separate = new wxCheckBox(sizer->GetStaticBox(), wxID_ANY, _("Open separate pad screen"));
|
||||
option_sizer->Add(m_separate, 0, wxALL, 5);
|
||||
|
||||
m_update = new wxCheckBox(sizer->GetStaticBox(), wxID_ANY, _("Automatically check for updates"));
|
||||
option_sizer->Add(m_update, 0, wxALL, 5);
|
||||
|
||||
sizer->Add(option_sizer, 1, wxEXPAND, 5);
|
||||
page2_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
|
||||
{
|
||||
auto* sizer = new wxFlexGridSizer(0, 3, 0, 0);
|
||||
sizer->AddGrowableCol(1);
|
||||
sizer->AddGrowableRow(0);
|
||||
sizer->SetFlexibleDirection(wxBOTH);
|
||||
sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_ALL);
|
||||
|
||||
m_dont_show = new wxCheckBox(result, wxID_ANY, _("Don't show this again"));
|
||||
m_dont_show->SetValue(true);
|
||||
sizer->Add(m_dont_show, 0, wxALIGN_BOTTOM | wxALL, 5);
|
||||
|
||||
auto* previous = new wxButton(result, wxID_ANY, _("Previous"));
|
||||
previous->Bind(wxEVT_BUTTON, [this](const auto&) {m_notebook->SetSelection(0); });
|
||||
sizer->Add(previous, 0, wxALIGN_BOTTOM | wxALIGN_RIGHT | wxALL, 5);
|
||||
|
||||
auto* close = new wxButton(result, wxID_ANY, _("Close"));
|
||||
close->Bind(wxEVT_BUTTON, [this](const auto&){ Close(); });
|
||||
sizer->Add(close, 1, wxALIGN_BOTTOM | wxALIGN_RIGHT | wxALL, 5);
|
||||
|
||||
page2_sizer->Add(sizer, 1, wxEXPAND | wxLEFT, 5);
|
||||
}
|
||||
|
||||
result->SetSizer(page2_sizer);
|
||||
return result;
|
||||
}
|
||||
|
||||
void GettingStartedDialog::ApplySettings()
|
||||
{
|
||||
auto& config = GetConfig();
|
||||
m_fullscreen->SetValue(config.fullscreen.GetValue());
|
||||
m_update->SetValue(config.check_update.GetValue());
|
||||
m_separate->SetValue(config.pad_open.GetValue());
|
||||
m_dont_show->SetValue(true); // we want it always enabled by default
|
||||
m_mlc_folder->SetPath(config.mlc_path.GetValue());
|
||||
|
||||
try
|
||||
{
|
||||
const auto pconfig = PermanentConfig::Load();
|
||||
if(!pconfig.custom_mlc_path.empty())
|
||||
{
|
||||
m_mlc_folder->SetPath(wxString::FromUTF8(pconfig.custom_mlc_path));
|
||||
m_prev_mlc_warning->Show(true);
|
||||
}
|
||||
}
|
||||
catch (const PSDisabledException&) {}
|
||||
catch (...) {}
|
||||
}
|
||||
|
||||
void GettingStartedDialog::UpdateWindowSize()
|
||||
{
|
||||
for (auto it = m_notebook->GetChildren().GetFirst(); it; it = it->GetNext())
|
||||
{
|
||||
it->GetData()->Layout();
|
||||
}
|
||||
m_notebook->Layout();
|
||||
Layout();
|
||||
Fit();
|
||||
}
|
||||
|
||||
void GettingStartedDialog::OnClose(wxCloseEvent& event)
|
||||
{
|
||||
event.Skip();
|
||||
|
||||
auto& config = GetConfig();
|
||||
config.fullscreen = m_fullscreen->GetValue();
|
||||
config.check_update = m_update->GetValue();
|
||||
config.pad_open = m_separate->GetValue();
|
||||
config.did_show_graphic_pack_download = m_dont_show->GetValue();
|
||||
|
||||
const fs::path gamePath = wxHelper::MakeFSPath(m_game_path->GetPath());
|
||||
if (!gamePath.empty() && fs::exists(gamePath))
|
||||
{
|
||||
const auto it = std::find(config.game_paths.cbegin(), config.game_paths.cend(), gamePath);
|
||||
if (it == config.game_paths.cend())
|
||||
{
|
||||
config.game_paths.emplace_back(gamePath.generic_wstring());
|
||||
m_game_path_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
const fs::path mlcPath = wxHelper::MakeFSPath(m_mlc_folder->GetPath());
|
||||
if(config.mlc_path.GetValue() != mlcPath && (mlcPath.empty() || fs::exists(mlcPath)))
|
||||
{
|
||||
config.SetMLCPath(mlcPath.generic_wstring(), false);
|
||||
m_mlc_changed = true;
|
||||
}
|
||||
|
||||
g_config.Save();
|
||||
|
||||
if(m_mlc_changed)
|
||||
CemuApp::CreateDefaultFiles();
|
||||
|
||||
CafeTitleList::ClearScanPaths();
|
||||
for (auto& it : GetConfig().game_paths)
|
||||
CafeTitleList::AddScanPath(it);
|
||||
CafeTitleList::Refresh();
|
||||
}
|
||||
|
||||
|
||||
GettingStartedDialog::GettingStartedDialog(wxWindow* parent)
|
||||
: wxDialog(parent, wxID_ANY, _("Getting started"), wxDefaultPosition, { 740,530 }, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
|
||||
{
|
||||
//this->SetSizeHints(wxDefaultSize, { 740,530 });
|
||||
|
||||
auto* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_notebook = new wxSimplebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0);
|
||||
|
||||
auto* m_page1 = CreatePage1();
|
||||
m_notebook->AddPage(m_page1, wxEmptyString, false);
|
||||
|
||||
auto* m_page2 = CreatePage2();
|
||||
m_notebook->AddPage(m_page2, wxEmptyString, false);
|
||||
|
||||
sizer->Add(m_notebook, 1, wxEXPAND | wxALL, 5);
|
||||
|
||||
this->SetSizer(sizer);
|
||||
this->Centre(wxBOTH);
|
||||
this->Bind(wxEVT_CLOSE_WINDOW, &GettingStartedDialog::OnClose, this);
|
||||
|
||||
ApplySettings();
|
||||
UpdateWindowSize();
|
||||
}
|
||||
|
||||
void GettingStartedDialog::OnDownloadGPs(wxCommandEvent& event)
|
||||
{
|
||||
DownloadGraphicPacksWindow dialog(this);
|
||||
dialog.ShowModal();
|
||||
|
||||
GraphicPacksWindow2::RefreshGraphicPacks();
|
||||
|
||||
wxMessageDialog ask_dialog(this, _("Do you want to view the downloaded graphic packs?"), _("Graphic packs"), wxCENTRE | wxYES_NO);
|
||||
if (ask_dialog.ShowModal() == wxID_YES)
|
||||
{
|
||||
GraphicPacksWindow2 window(this, 0);
|
||||
window.ShowModal();
|
||||
}
|
||||
}
|
||||
|
||||
void GettingStartedDialog::OnInputSettings(wxCommandEvent& event)
|
||||
{
|
||||
InputSettings2 dialog(this);
|
||||
dialog.ShowModal();
|
||||
}
|
||||
|
||||
void GettingStartedDialog::OnMLCPathChar(wxKeyEvent& event)
|
||||
{
|
||||
//if (LaunchSettings::GetMLCPath().has_value())
|
||||
// return;
|
||||
|
||||
if (event.GetKeyCode() == WXK_DELETE || event.GetKeyCode() == WXK_BACK)
|
||||
{
|
||||
m_mlc_folder->GetTextCtrl()->SetValue(wxEmptyString);
|
||||
if(m_prev_mlc_warning->IsShown())
|
||||
{
|
||||
m_prev_mlc_warning->Show(false);
|
||||
UpdateWindowSize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
44
src/gui/GettingStartedDialog.h
Normal file
44
src/gui/GettingStartedDialog.h
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/checkbox.h>
|
||||
#include <wx/filepicker.h>
|
||||
#include <wx/statbmp.h>
|
||||
#include <wx/simplebook.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/panel.h>
|
||||
|
||||
class GettingStartedDialog : public wxDialog
|
||||
{
|
||||
public:
|
||||
GettingStartedDialog(wxWindow* parent = nullptr);
|
||||
|
||||
[[nodiscard]] bool HasGamePathChanged() const { return m_game_path_changed; }
|
||||
[[nodiscard]] bool HasMLCChanged() const { return m_mlc_changed; }
|
||||
|
||||
private:
|
||||
wxPanel* CreatePage1();
|
||||
wxPanel* CreatePage2();
|
||||
void ApplySettings();
|
||||
void UpdateWindowSize();
|
||||
|
||||
void OnClose(wxCloseEvent& event);
|
||||
void OnDownloadGPs(wxCommandEvent& event);
|
||||
void OnInputSettings(wxCommandEvent& event);
|
||||
void OnMLCPathChar(wxKeyEvent& event);
|
||||
|
||||
wxSimplebook* m_notebook;
|
||||
wxCheckBox* m_fullscreen;
|
||||
wxCheckBox* m_separate;
|
||||
wxCheckBox* m_update;
|
||||
wxCheckBox* m_dont_show;
|
||||
|
||||
wxStaticBoxSizer* m_mlc_box_sizer;
|
||||
wxStaticText* m_prev_mlc_warning;
|
||||
wxDirPickerCtrl* m_mlc_folder;
|
||||
wxDirPickerCtrl* m_game_path;
|
||||
|
||||
bool m_game_path_changed = false;
|
||||
bool m_mlc_changed = false;
|
||||
};
|
||||
|
||||
675
src/gui/GraphicPacksWindow2.cpp
Normal file
675
src/gui/GraphicPacksWindow2.cpp
Normal file
|
|
@ -0,0 +1,675 @@
|
|||
#include "gui/wxgui.h"
|
||||
#include "gui/GraphicPacksWindow2.h"
|
||||
#include "gui/DownloadGraphicPacksWindow.h"
|
||||
#include "Cafe/GraphicPack/GraphicPack2.h"
|
||||
#include "config/CemuConfig.h"
|
||||
#include "Cafe/GraphicPack/GraphicPack.h"
|
||||
|
||||
#include "Cafe/HW/Latte/Core/LatteAsyncCommands.h"
|
||||
|
||||
#include "Cafe/CafeSystem.h"
|
||||
#include "Cafe/TitleList/TitleList.h"
|
||||
|
||||
#if BOOST_OS_LINUX > 0
|
||||
#include "resource/linux/resources.h"
|
||||
#endif
|
||||
|
||||
// main.cpp
|
||||
bool IsCemuhookLoaded();
|
||||
|
||||
class wxGraphicPackData : public wxTreeItemData
|
||||
{
|
||||
public:
|
||||
wxGraphicPackData(GraphicPackPtr pack)
|
||||
: m_pack(std::move(pack)) { }
|
||||
|
||||
const GraphicPackPtr& GetGraphicPack() const { return m_pack; }
|
||||
|
||||
private:
|
||||
GraphicPackPtr m_pack;
|
||||
};
|
||||
|
||||
void GraphicPacksWindow2::FillGraphicPackList() const
|
||||
{
|
||||
wxWindowUpdateLocker lock(m_graphic_pack_tree);
|
||||
|
||||
m_graphic_pack_tree->DeleteAllItems();
|
||||
auto graphic_packs = GraphicPack2::GetGraphicPacks();
|
||||
|
||||
const auto root = m_graphic_pack_tree->AddRoot("Root");
|
||||
|
||||
const bool has_filter = !m_filter.empty();
|
||||
|
||||
for(auto& p : graphic_packs)
|
||||
{
|
||||
// filter graphic packs by given title id
|
||||
if (m_filter_installed_games && !m_installed_games.empty())
|
||||
{
|
||||
bool found = false;
|
||||
for (uint64 titleId : p->GetTitleIds())
|
||||
{
|
||||
if (std::find(m_installed_games.cbegin(), m_installed_games.cend(), titleId) != m_installed_games.cend())
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
continue;
|
||||
}
|
||||
|
||||
// filter graphic packs by given title id
|
||||
if(has_filter)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
if (boost::icontains(p->GetPath(), m_filter))
|
||||
found = true;
|
||||
else
|
||||
{
|
||||
for (uint64 titleId : p->GetTitleIds())
|
||||
{
|
||||
if (boost::icontains(fmt::format("{:x}", titleId), m_filter))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& path = p->GetPath();
|
||||
auto tokens = TokenizeView(path, '/');
|
||||
auto node = root;
|
||||
for(size_t i=0; i<tokens.size(); i++)
|
||||
{
|
||||
auto& token = tokens[i];
|
||||
const auto parent_node = node;
|
||||
if (i < (tokens.size() - 1))
|
||||
{
|
||||
node = FindTreeItem(parent_node, wxString(token.data(), token.length()));
|
||||
if (!node.IsOk())
|
||||
{
|
||||
node = m_graphic_pack_tree->AppendItem(parent_node, wxString(token.data(), token.length()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// last token
|
||||
// if a node with same name already exists, add a number suffix
|
||||
for (sint32 s = 0; s < 999; s++)
|
||||
{
|
||||
wxString nodeName(token.data(), token.length());
|
||||
if (s > 0)
|
||||
nodeName.append(fmt::format(" #{}", s + 1));
|
||||
|
||||
node = FindTreeItem(parent_node, nodeName);
|
||||
if (!node.IsOk())
|
||||
{
|
||||
node = m_graphic_pack_tree->AppendItem(parent_node, nodeName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(node.IsOk() && node != root)
|
||||
{
|
||||
m_graphic_pack_tree->SetItemData(node, new wxGraphicPackData(p));
|
||||
bool canEnable = true;
|
||||
|
||||
if (p->GetVersion() == 3)
|
||||
{
|
||||
auto tmp_text = m_graphic_pack_tree->GetItemText(node);
|
||||
m_graphic_pack_tree->SetItemText(node, tmp_text + " (may not be compatible with Vulkan)");
|
||||
}
|
||||
else if (p->GetVersion() != 3 && p->GetVersion() != 4 && p->GetVersion() != 5 && p->GetVersion() != 6 && p->GetVersion() != GraphicPack2::GFXPACK_VERSION_7)
|
||||
{
|
||||
auto tmp_text = m_graphic_pack_tree->GetItemText(node);
|
||||
m_graphic_pack_tree->SetItemText(node, tmp_text + " (Unsupported version)");
|
||||
m_graphic_pack_tree->SetItemTextColour(node, 0x0000CC);
|
||||
canEnable = false;
|
||||
}
|
||||
else if (p->IsActivated())
|
||||
m_graphic_pack_tree->SetItemTextColour(node, 0x009900);
|
||||
|
||||
m_graphic_pack_tree->MakeCheckable(node, p->IsEnabled());
|
||||
if (!canEnable)
|
||||
m_graphic_pack_tree->DisableCheckBox(node);
|
||||
}
|
||||
}
|
||||
|
||||
m_graphic_pack_tree->Sort(root, true);
|
||||
|
||||
if (!m_filter.empty())
|
||||
{
|
||||
size_t counter = 0;
|
||||
ExpandChildren({ root }, counter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void GraphicPacksWindow2::GetChildren(const wxTreeItemId& id, std::vector<wxTreeItemId>& children) const
|
||||
{
|
||||
wxTreeItemIdValue cookie;
|
||||
wxTreeItemId child = m_graphic_pack_tree->GetFirstChild(id, cookie);
|
||||
while (child.IsOk())
|
||||
{
|
||||
children.emplace_back(child);
|
||||
child = m_graphic_pack_tree->GetNextChild(child, cookie);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicPacksWindow2::ExpandChildren(const std::vector<wxTreeItemId>& ids, size_t& counter) const
|
||||
{
|
||||
std::vector<wxTreeItemId> children;
|
||||
for (const auto& id : ids)
|
||||
GetChildren(id, children);
|
||||
|
||||
counter += children.size();
|
||||
if (counter >= 30 || children.empty())
|
||||
return;
|
||||
|
||||
for (const auto& id : ids)
|
||||
{
|
||||
if(id != m_graphic_pack_tree->GetRootItem() && m_graphic_pack_tree->HasChildren(id))
|
||||
m_graphic_pack_tree->Expand(id);
|
||||
}
|
||||
|
||||
ExpandChildren(children, counter);
|
||||
}
|
||||
|
||||
void GraphicPacksWindow2::RefreshGraphicPacks()
|
||||
{
|
||||
GraphicPack2::ClearGraphicPacks();
|
||||
graphicPack_loadAll();
|
||||
}
|
||||
|
||||
GraphicPacksWindow2::GraphicPacksWindow2(wxWindow* parent, uint64_t title_id_filter)
|
||||
: wxDialog(parent, wxID_ANY, _("Graphic packs"), wxDefaultPosition, wxSize(1000,670), wxCLOSE_BOX | wxCLIP_CHILDREN | wxCAPTION | wxRESIZE_BORDER),
|
||||
m_installed_games(CafeTitleList::GetAllTitleIds())
|
||||
{
|
||||
if (title_id_filter != 0)
|
||||
m_filter = fmt::format("{:x}", title_id_filter);
|
||||
|
||||
m_filter_installed_games = !m_installed_games.empty();
|
||||
|
||||
SetIcon(wxICON(X_BOX));
|
||||
SetMinSize(wxSize(500, 400));
|
||||
auto main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_splitter_window = new wxSplitterWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_3D);
|
||||
m_splitter_window->Bind(wxEVT_SIZE, &GraphicPacksWindow2::OnSizeChanged, this);
|
||||
m_splitter_window->Bind(wxEVT_SPLITTER_SASH_POS_CHANGED, &GraphicPacksWindow2::SashPositionChanged, this);
|
||||
|
||||
// left side
|
||||
auto left_panel = new wxPanel(m_splitter_window);
|
||||
{
|
||||
auto sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
wxFlexGridSizer* filter_row = new wxFlexGridSizer(0, 3, 0, 0);
|
||||
filter_row->AddGrowableCol(1);
|
||||
filter_row->SetFlexibleDirection(wxBOTH);
|
||||
filter_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
||||
|
||||
const auto text = new wxStaticText(left_panel, wxID_ANY, _("Filter"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
text->Wrap(-1);
|
||||
filter_row->Add(text, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
|
||||
m_filter_text = new wxTextCtrl(left_panel, wxID_ANY, m_filter, wxDefaultPosition, wxDefaultSize, 0);
|
||||
filter_row->Add(m_filter_text, 0, wxALL | wxEXPAND, 5);
|
||||
m_filter_text->Bind(wxEVT_COMMAND_TEXT_UPDATED, &GraphicPacksWindow2::OnFilterUpdate, this);
|
||||
|
||||
m_installed_games_only = new wxCheckBox(left_panel, wxID_ANY, _("Installed games"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
m_installed_games_only->SetValue(m_filter_installed_games);
|
||||
filter_row->Add(m_installed_games_only, 0, wxALL | wxEXPAND, 5);
|
||||
m_installed_games_only->Bind(wxEVT_CHECKBOX, &GraphicPacksWindow2::OnInstalledGamesChanged, this);
|
||||
if (m_installed_games.empty())
|
||||
m_installed_games_only->Disable();
|
||||
|
||||
sizer->Add(filter_row, 0, wxEXPAND, 5);
|
||||
|
||||
m_graphic_pack_tree = new wxCheckTree(left_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTR_DEFAULT_STYLE | wxTR_HIDE_ROOT);
|
||||
m_graphic_pack_tree->Bind(wxEVT_TREE_SEL_CHANGED, &GraphicPacksWindow2::OnTreeSelectionChanged, this);
|
||||
m_graphic_pack_tree->Bind(wxEVT_CHECKTREE_CHOICE, &GraphicPacksWindow2::OnTreeChoiceChanged, this);
|
||||
//m_graphic_pack_tree->SetMinSize(wxSize(600, 400));
|
||||
sizer->Add(m_graphic_pack_tree, 1, wxEXPAND | wxALL, 5);
|
||||
|
||||
left_panel->SetSizerAndFit(sizer);
|
||||
}
|
||||
|
||||
// right side
|
||||
m_right_panel = new wxPanel(m_splitter_window, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxNO_BORDER | wxFULL_REPAINT_ON_RESIZE);
|
||||
{
|
||||
auto* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
{
|
||||
m_gp_options = new wxScrolled<wxPanel>(m_right_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxNO_BORDER | wxVSCROLL);
|
||||
m_gp_options->SetScrollRate(-1, 10);
|
||||
|
||||
auto* inner_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
{
|
||||
auto* box = new wxStaticBox(m_gp_options, wxID_ANY, _("Graphic pack"));
|
||||
auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
|
||||
|
||||
m_graphic_pack_name = new wxStaticText(box, wxID_ANY, wxEmptyString);
|
||||
box_sizer->Add(m_graphic_pack_name, 1, wxEXPAND | wxALL, 5);
|
||||
|
||||
inner_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
|
||||
}
|
||||
|
||||
{
|
||||
auto* box = new wxStaticBox(m_gp_options, wxID_ANY, _("Description"));
|
||||
auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
|
||||
|
||||
m_graphic_pack_description = new wxStaticText(box, wxID_ANY, wxEmptyString);
|
||||
box_sizer->Add(m_graphic_pack_description, 1, wxEXPAND | wxALL, 5);
|
||||
|
||||
inner_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
|
||||
}
|
||||
|
||||
m_preset_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
inner_sizer->Add(m_preset_sizer, 0, wxEXPAND, 0);
|
||||
|
||||
{
|
||||
auto* box = new wxStaticBox(m_gp_options, wxID_ANY, _("Control"));
|
||||
auto* box_sizer = new wxStaticBoxSizer(box, wxHORIZONTAL);
|
||||
|
||||
m_reload_shaders = new wxButton(box, wxID_ANY, _("Reload edited shaders"));
|
||||
m_reload_shaders->Bind(wxEVT_BUTTON, &GraphicPacksWindow2::OnReloadShaders, this);
|
||||
m_reload_shaders->Disable();
|
||||
box_sizer->Add(m_reload_shaders, 0, wxEXPAND | wxALL, 5);
|
||||
|
||||
inner_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
|
||||
}
|
||||
|
||||
inner_sizer->Add(new wxStaticText(m_gp_options, wxID_ANY, wxEmptyString), 1, wxALL | wxEXPAND, 5);
|
||||
|
||||
m_gp_options->SetSizerAndFit(inner_sizer);
|
||||
m_gp_options->Hide();
|
||||
|
||||
sizer->Add(m_gp_options, 1, wxEXPAND | wxRESERVE_SPACE_EVEN_IF_HIDDEN);
|
||||
}
|
||||
|
||||
|
||||
sizer->Add(new wxStaticLine(m_right_panel, wxID_ANY), 0, wxLEFT|wxRIGHT | wxEXPAND, 3);
|
||||
|
||||
auto* row = new wxBoxSizer(wxHORIZONTAL);
|
||||
m_update_graphicPacks = new wxButton(m_right_panel, wxID_ANY, _("Download latest community graphic packs"));
|
||||
m_update_graphicPacks->Bind(wxEVT_BUTTON, &GraphicPacksWindow2::OnCheckForUpdates, this);
|
||||
row->Add(m_update_graphicPacks, 0, wxALL, 5);
|
||||
|
||||
sizer->Add(row, 0, wxALL | wxEXPAND, 5);
|
||||
|
||||
m_right_panel->SetSizerAndFit(sizer);
|
||||
}
|
||||
|
||||
m_splitter_window->SetMinimumPaneSize(50);
|
||||
m_splitter_window->SplitVertically(left_panel, m_right_panel, (sint32)(m_ratio * m_splitter_window->GetParent()->GetSize().GetWidth()));
|
||||
main_sizer->Add(m_splitter_window, 1, wxEXPAND, 5);
|
||||
|
||||
|
||||
m_info_bar = new wxInfoBar(this);
|
||||
m_info_bar->SetShowHideEffects(wxSHOW_EFFECT_BLEND, wxSHOW_EFFECT_BLEND);
|
||||
m_info_bar->SetEffectDuration(500);
|
||||
main_sizer->Add(m_info_bar, 0, wxALL | wxEXPAND, 5);
|
||||
|
||||
SetSizer(main_sizer);
|
||||
|
||||
FillGraphicPackList();
|
||||
}
|
||||
|
||||
void GraphicPacksWindow2::SaveStateToConfig()
|
||||
{
|
||||
auto& data = g_config.data();
|
||||
data.graphic_pack_entries.clear();
|
||||
|
||||
for (const auto& gp : GraphicPack2::GetGraphicPacks())
|
||||
{
|
||||
auto filename = MakeRelativePath(gp->GetFilename()).lexically_normal();
|
||||
if (gp->IsEnabled())
|
||||
{
|
||||
data.graphic_pack_entries.try_emplace(filename);
|
||||
auto& it = data.graphic_pack_entries[filename];
|
||||
// otherwise store all selected presets
|
||||
for (const auto& preset : gp->GetActivePresets())
|
||||
it.try_emplace(preset->category, preset->name);
|
||||
}
|
||||
else if(gp->IsDefaultEnabled())
|
||||
{
|
||||
// save that its disabled
|
||||
data.graphic_pack_entries.try_emplace(filename);
|
||||
auto& it = data.graphic_pack_entries[filename];
|
||||
it.try_emplace("_disabled", "false");
|
||||
}
|
||||
}
|
||||
|
||||
g_config.Save();
|
||||
}
|
||||
|
||||
GraphicPacksWindow2::~GraphicPacksWindow2()
|
||||
{
|
||||
m_graphic_pack_tree->Unbind(wxEVT_CHECKTREE_CHOICE, &GraphicPacksWindow2::OnTreeSelectionChanged, this);
|
||||
m_graphic_pack_tree->Unbind(wxEVT_CHECKTREE_CHOICE, &GraphicPacksWindow2::OnTreeChoiceChanged, this);
|
||||
// m_active_preset->Unbind(wxEVT_CHOICE, &GraphicPacksWindow2::OnActivePresetChanged, this);
|
||||
m_reload_shaders->Unbind(wxEVT_BUTTON, &GraphicPacksWindow2::OnReloadShaders, this);
|
||||
|
||||
SaveStateToConfig();
|
||||
}
|
||||
|
||||
wxTreeItemId GraphicPacksWindow2::FindTreeItem(const wxTreeItemId& root, const wxString& text) const
|
||||
{
|
||||
wxTreeItemIdValue cookie;
|
||||
for(auto item = m_graphic_pack_tree->GetFirstChild(root, cookie); item.IsOk(); item = m_graphic_pack_tree->GetNextSibling(item))
|
||||
{
|
||||
if (m_graphic_pack_tree->GetItemText(item) == text)
|
||||
return item;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void GraphicPacksWindow2::LoadPresetSelections(const GraphicPackPtr& gp)
|
||||
{
|
||||
std::vector<std::string> order;
|
||||
auto presets = gp->GetCategorizedPresets(order);
|
||||
for(const auto& category : order)
|
||||
{
|
||||
const auto& entry = presets[category];
|
||||
|
||||
// test if any preset is visible and update its status
|
||||
if (std::none_of(entry.cbegin(), entry.cend(), [gp](const auto& p)
|
||||
{
|
||||
return p->visible;
|
||||
}))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
wxString label(category.empty() ? _("Active preset") : wxString(category));
|
||||
auto* box = new wxStaticBox(m_preset_sizer->GetContainingWindow(), wxID_ANY, label);
|
||||
auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
|
||||
|
||||
auto* preset = new wxChoice(box, wxID_ANY);
|
||||
preset->SetClientObject(new wxStringClientData(category));
|
||||
preset->Bind(wxEVT_CHOICE, &GraphicPacksWindow2::OnActivePresetChanged, this);
|
||||
|
||||
std::optional<std::string> active_preset;
|
||||
for (auto& pentry : entry)
|
||||
{
|
||||
if (!pentry->visible)
|
||||
continue;
|
||||
|
||||
preset->AppendString(pentry->name);
|
||||
if (pentry->active)
|
||||
active_preset = pentry->name;
|
||||
}
|
||||
|
||||
if (active_preset)
|
||||
preset->SetStringSelection(active_preset.value());
|
||||
else if (preset->GetCount() > 0)
|
||||
preset->SetSelection(0);
|
||||
|
||||
box_sizer->Add(preset, 1, wxEXPAND | wxALL, 5);
|
||||
|
||||
m_preset_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicPacksWindow2::OnTreeSelectionChanged(wxTreeEvent& event)
|
||||
{
|
||||
wxWindowUpdateLocker lock(this);
|
||||
|
||||
const auto selection = m_graphic_pack_tree->GetSelection();
|
||||
if (selection.IsOk())
|
||||
{
|
||||
const auto data = dynamic_cast<wxGraphicPackData*>(m_graphic_pack_tree->GetItemData(selection));
|
||||
if (data)
|
||||
{
|
||||
if(!m_gp_options->IsShown())
|
||||
m_gp_options->Show();
|
||||
|
||||
const auto& gp = data->GetGraphicPack();
|
||||
if (gp != m_shown_graphic_pack)
|
||||
{
|
||||
m_preset_sizer->Clear(true);
|
||||
m_gp_name = gp->GetName();
|
||||
m_graphic_pack_name->SetLabel(m_gp_name);
|
||||
|
||||
if (gp->GetDescription().empty())
|
||||
m_gp_description = _("This graphic pack has no description");
|
||||
else
|
||||
m_gp_description = gp->GetDescription();
|
||||
|
||||
m_graphic_pack_description->SetLabel(m_gp_description);
|
||||
|
||||
LoadPresetSelections(gp);
|
||||
|
||||
m_reload_shaders->Enable(gp->HasShaders());
|
||||
|
||||
m_shown_graphic_pack = gp;
|
||||
|
||||
m_graphic_pack_name->Wrap(m_graphic_pack_name->GetParent()->GetClientSize().GetWidth() - 10);
|
||||
m_graphic_pack_name->GetGrandParent()->Layout();
|
||||
|
||||
m_graphic_pack_description->Wrap(m_graphic_pack_description->GetParent()->GetClientSize().GetWidth() - 10);
|
||||
m_graphic_pack_description->GetGrandParent()->Layout();
|
||||
|
||||
m_right_panel->FitInside();
|
||||
m_right_panel->Layout();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_preset_sizer->Clear(true);
|
||||
m_graphic_pack_name->SetLabel(wxEmptyString);
|
||||
m_graphic_pack_description->SetLabel(wxEmptyString);
|
||||
|
||||
m_reload_shaders->Disable();
|
||||
|
||||
m_shown_graphic_pack.reset();
|
||||
|
||||
m_gp_options->Hide();
|
||||
m_right_panel->FitInside();
|
||||
m_right_panel->Layout();
|
||||
}
|
||||
|
||||
void GraphicPacksWindow2::OnTreeChoiceChanged(wxTreeEvent& event)
|
||||
{
|
||||
auto item = event.GetItem();
|
||||
if (!item.IsOk())
|
||||
return;
|
||||
|
||||
const bool state = event.GetExtraLong() != 0;
|
||||
|
||||
const auto data = dynamic_cast<wxGraphicPackData*>(m_graphic_pack_tree->GetItemData(item));
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
auto& graphic_pack = data->GetGraphicPack();
|
||||
graphic_pack->SetEnabled(state);
|
||||
|
||||
bool has_texture_rules = false;
|
||||
if (CafeSystem::IsTitleRunning() && graphic_pack->ContainsTitleId(CafeSystem::GetForegroundTitleId()))
|
||||
{
|
||||
if (state)
|
||||
{
|
||||
GraphicPack2::ActivateGraphicPack(graphic_pack);
|
||||
has_texture_rules = !graphic_pack->GetTextureRules().empty();
|
||||
|
||||
if (!has_texture_rules)
|
||||
{
|
||||
ReloadPack(graphic_pack);
|
||||
m_graphic_pack_tree->SetItemTextColour(item, 0x009900);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
has_texture_rules = !graphic_pack->GetTextureRules().empty();
|
||||
|
||||
if (!has_texture_rules)
|
||||
{
|
||||
DeleteShadersFromRuntimeCache(graphic_pack);
|
||||
m_graphic_pack_tree->SetItemTextColour(item, *wxBLACK);
|
||||
}
|
||||
|
||||
GraphicPack2::DeactivateGraphicPack(graphic_pack);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_info_bar->IsShown() && has_texture_rules)
|
||||
m_info_bar->ShowMessage(_("Restart of Cemu required for changes to take effect"));
|
||||
|
||||
// also change selection to activated gp
|
||||
m_graphic_pack_tree->SelectItem(item);
|
||||
}
|
||||
|
||||
void GraphicPacksWindow2::OnActivePresetChanged(wxCommandEvent& event)
|
||||
{
|
||||
if (!m_shown_graphic_pack)
|
||||
return;
|
||||
|
||||
const auto obj = wxDynamicCast(event.GetEventObject(), wxChoice);
|
||||
wxASSERT(obj);
|
||||
const auto string_data = dynamic_cast<wxStringClientData*>(obj->GetClientObject());
|
||||
wxASSERT(string_data);
|
||||
const auto preset = obj->GetStringSelection().ToStdString();
|
||||
if(m_shown_graphic_pack->SetActivePreset(string_data->GetData().c_str().AsChar(), preset))
|
||||
{
|
||||
wxWindowUpdateLocker lock(this);
|
||||
m_preset_sizer->Clear(true);
|
||||
LoadPresetSelections(m_shown_graphic_pack);
|
||||
//m_preset_sizer->GetContainingWindow()->Layout();
|
||||
//m_right_panel->FitInside();
|
||||
m_right_panel->FitInside();
|
||||
m_right_panel->Layout();
|
||||
}
|
||||
|
||||
if (m_shown_graphic_pack->GetTextureRules().empty())
|
||||
ReloadPack(m_shown_graphic_pack);
|
||||
else if (!m_info_bar->IsShown())
|
||||
m_info_bar->ShowMessage(_("Restart of Cemu required for changes to take effect"));
|
||||
}
|
||||
|
||||
void GraphicPacksWindow2::OnReloadShaders(wxCommandEvent& event)
|
||||
{
|
||||
if (m_shown_graphic_pack)
|
||||
ReloadPack(m_shown_graphic_pack);
|
||||
}
|
||||
|
||||
void GraphicPacksWindow2::OnCheckForUpdates(wxCommandEvent& event)
|
||||
{
|
||||
DownloadGraphicPacksWindow frame(this);
|
||||
SaveStateToConfig();
|
||||
const int updateResult = frame.ShowModal();
|
||||
if (updateResult == wxID_OK)
|
||||
{
|
||||
if (!CafeSystem::IsTitleRunning())
|
||||
{
|
||||
std::vector<GraphicPackPtr> old_packs = GraphicPack2::GetGraphicPacks();
|
||||
RefreshGraphicPacks();
|
||||
FillGraphicPackList();
|
||||
|
||||
// check if enabled graphic packs are lost:
|
||||
const auto& new_packs = GraphicPack2::GetGraphicPacks();
|
||||
std::stringstream str;
|
||||
for(const auto& p : old_packs)
|
||||
{
|
||||
if (!p->IsEnabled())
|
||||
continue;
|
||||
|
||||
const auto it = std::find_if(new_packs.cbegin(), new_packs.cend(), [&p](const auto& gp)
|
||||
{
|
||||
return gp->GetFilename() == p->GetFilename();
|
||||
});
|
||||
|
||||
if(it == new_packs.cend())
|
||||
{
|
||||
str << p->GetPath() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
const auto packs = str.str();
|
||||
if(!packs.empty())
|
||||
{
|
||||
wxMessageBox(fmt::format("{}\n \n{} \n{}", _("This update removed or renamed the following graphic packs:").ToStdString(), packs, _("You may need to set them up again.").ToStdString()),
|
||||
_("Warning"), wxOK | wxCENTRE | wxICON_INFORMATION, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicPacksWindow2::OnSizeChanged(wxSizeEvent& event)
|
||||
{
|
||||
const auto obj = (wxSplitterWindow*)event.GetEventObject();
|
||||
wxASSERT(obj);
|
||||
|
||||
const auto width = std::max(obj->GetMinimumPaneSize(), obj->GetParent()->GetClientSize().GetWidth());
|
||||
obj->SetSashPosition((sint32)(m_ratio * width));
|
||||
|
||||
if (!m_gp_name.empty())
|
||||
m_graphic_pack_name->SetLabel(m_gp_name);
|
||||
|
||||
if (!m_gp_description.empty())
|
||||
m_graphic_pack_description->SetLabel(m_gp_description);
|
||||
|
||||
m_graphic_pack_name->Wrap(m_graphic_pack_name->GetParent()->GetClientSize().GetWidth() - 10);
|
||||
m_graphic_pack_description->Wrap(m_graphic_pack_description->GetParent()->GetClientSize().GetWidth() - 10);
|
||||
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void GraphicPacksWindow2::SashPositionChanged(wxEvent& event)
|
||||
{
|
||||
const auto obj = (wxSplitterWindow*)event.GetEventObject();
|
||||
wxASSERT(obj);
|
||||
|
||||
const auto width = std::max(obj->GetMinimumPaneSize(), obj->GetParent()->GetClientSize().GetWidth());
|
||||
m_ratio = (float)obj->GetSashPosition() / width;
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void GraphicPacksWindow2::OnFilterUpdate(wxEvent& event)
|
||||
{
|
||||
m_filter = m_filter_text->GetValue();
|
||||
FillGraphicPackList();
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void GraphicPacksWindow2::OnInstalledGamesChanged(wxCommandEvent& event)
|
||||
{
|
||||
m_filter_installed_games = m_installed_games_only->GetValue();
|
||||
FillGraphicPackList();
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void GraphicPacksWindow2::ReloadPack(const GraphicPackPtr& graphic_pack) const
|
||||
{
|
||||
if (graphic_pack->HasShaders() || graphic_pack->HasPatches() || graphic_pack->HasCustomVSyncFrequency())
|
||||
{
|
||||
if (graphic_pack->Reload())
|
||||
{
|
||||
DeleteShadersFromRuntimeCache(graphic_pack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicPacksWindow2::DeleteShadersFromRuntimeCache(const GraphicPackPtr& graphic_pack) const
|
||||
{
|
||||
for (const auto& shader : graphic_pack->GetCustomShaders())
|
||||
{
|
||||
LatteConst::ShaderType shaderType;
|
||||
if (shader.type == GraphicPack2::GP_SHADER_TYPE::VERTEX)
|
||||
shaderType = LatteConst::ShaderType::Vertex;
|
||||
else if (shader.type == GraphicPack2::GP_SHADER_TYPE::GEOMETRY)
|
||||
shaderType = LatteConst::ShaderType::Geometry;
|
||||
else if (shader.type == GraphicPack2::GP_SHADER_TYPE::PIXEL)
|
||||
shaderType = LatteConst::ShaderType::Pixel;
|
||||
LatteAsyncCommands_queueDeleteShader(shader.shader_base_hash, shader.shader_aux_hash, shaderType);
|
||||
}
|
||||
}
|
||||
|
||||
73
src/gui/GraphicPacksWindow2.h
Normal file
73
src/gui/GraphicPacksWindow2.h
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#pragma once
|
||||
|
||||
#include <wx/frame.h>
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/scrolwin.h>
|
||||
#include <wx/infobar.h>
|
||||
|
||||
#include "wxcomponents/checktree.h"
|
||||
|
||||
#include "Cafe/GraphicPack/GraphicPack2.h"
|
||||
|
||||
class wxSplitterWindow;
|
||||
class wxPanel;
|
||||
class wxButton;
|
||||
class wxChoice;
|
||||
|
||||
class GraphicPacksWindow2 : public wxDialog
|
||||
{
|
||||
public:
|
||||
GraphicPacksWindow2(wxWindow* parent, uint64_t title_id_filter);
|
||||
~GraphicPacksWindow2();
|
||||
|
||||
static void RefreshGraphicPacks();
|
||||
|
||||
private:
|
||||
std::string m_filter;
|
||||
bool m_filter_installed_games;
|
||||
std::vector<uint64_t> m_installed_games;
|
||||
|
||||
void FillGraphicPackList() const;
|
||||
void GetChildren(const wxTreeItemId& id, std::vector<wxTreeItemId>& children) const;
|
||||
void ExpandChildren(const std::vector<wxTreeItemId>& ids, size_t& counter) const;
|
||||
|
||||
wxSplitterWindow * m_splitter_window;
|
||||
|
||||
wxPanel* m_right_panel;
|
||||
wxScrolled<wxPanel>* m_gp_options;
|
||||
|
||||
wxCheckTree * m_graphic_pack_tree;
|
||||
wxTextCtrl* m_filter_text;
|
||||
wxCheckBox* m_installed_games_only;
|
||||
|
||||
wxStaticText* m_graphic_pack_name, *m_graphic_pack_description;
|
||||
wxBoxSizer* m_preset_sizer;
|
||||
std::vector<wxChoice*> m_active_preset;
|
||||
wxButton* m_reload_shaders;
|
||||
wxButton* m_update_graphicPacks;
|
||||
wxInfoBar* m_info_bar;
|
||||
|
||||
GraphicPackPtr m_shown_graphic_pack;
|
||||
std::string m_gp_name, m_gp_description;
|
||||
|
||||
float m_ratio = 0.55f;
|
||||
|
||||
wxTreeItemId FindTreeItem(const wxTreeItemId& root, const wxString& text) const;
|
||||
void LoadPresetSelections(const GraphicPackPtr& gp);
|
||||
|
||||
void OnTreeSelectionChanged(wxTreeEvent& event);
|
||||
void OnTreeChoiceChanged(wxTreeEvent& event);
|
||||
void OnActivePresetChanged(wxCommandEvent& event);
|
||||
void OnReloadShaders(wxCommandEvent& event);
|
||||
void OnCheckForUpdates(wxCommandEvent& event);
|
||||
void OnSizeChanged(wxSizeEvent& event);
|
||||
void SashPositionChanged(wxEvent& event);
|
||||
void OnFilterUpdate(wxEvent& event);
|
||||
void OnInstalledGamesChanged(wxCommandEvent& event);
|
||||
|
||||
void SaveStateToConfig();
|
||||
|
||||
void ReloadPack(const GraphicPackPtr& graphic_pack) const;
|
||||
void DeleteShadersFromRuntimeCache(const GraphicPackPtr& graphic_pack) const;
|
||||
|
||||
};
|
||||
100
src/gui/LoggingWindow.cpp
Normal file
100
src/gui/LoggingWindow.cpp
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
#include "gui/LoggingWindow.h"
|
||||
|
||||
#include "gui/helpers/wxLogEvent.h"
|
||||
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/wupdlock.h>
|
||||
|
||||
#include "gui/helpers/wxHelpers.h"
|
||||
|
||||
wxDEFINE_EVENT(EVT_LOG, wxLogEvent);
|
||||
|
||||
LoggingWindow* s_instance;
|
||||
|
||||
LoggingWindow::LoggingWindow(wxFrame* parent)
|
||||
: wxFrame(parent, wxID_ANY, _("Logging window"), wxDefaultPosition, wxSize(800, 600), wxDEFAULT_FRAME_STYLE | wxTAB_TRAVERSAL)
|
||||
{
|
||||
auto* sizer = new wxBoxSizer( wxVERTICAL );
|
||||
{
|
||||
auto filter_row = new wxBoxSizer( wxHORIZONTAL );
|
||||
|
||||
filter_row->Add(new wxStaticText( this, wxID_ANY, _("Filter")), 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
|
||||
|
||||
wxString choices[] = {"Coreinit File-Access", "Coreinit Thread-Synchronization", "Coreinit Memory", "GX2", "Audio", "Input", "Socket", "Save", "H264", "Texture Cache", "OpenGL"};
|
||||
m_filter = new wxComboBox( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, std::size(choices), choices, 0 );
|
||||
m_filter->Bind(wxEVT_COMBOBOX, &LoggingWindow::OnFilterChange, this);
|
||||
m_filter->Bind(wxEVT_TEXT, &LoggingWindow::OnFilterChange, this);
|
||||
filter_row->Add(m_filter, 1, wxALL, 5 );
|
||||
|
||||
m_filter_message = new wxCheckBox(this, wxID_ANY, _("Filter messages"));
|
||||
m_filter_message->Bind(wxEVT_CHECKBOX, &LoggingWindow::OnFilterMessageChange, this);
|
||||
filter_row->Add(m_filter_message, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
|
||||
|
||||
sizer->Add( filter_row, 0, wxEXPAND, 5 );
|
||||
}
|
||||
|
||||
m_log_list = new wxLogCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxScrolledWindowStyle|wxVSCROLL);//( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, NULL, wxLB_HSCROLL );
|
||||
sizer->Add( m_log_list, 1, wxALL | wxEXPAND, 5 );
|
||||
|
||||
this->SetSizer( sizer );
|
||||
this->Layout();
|
||||
|
||||
this->Bind(EVT_LOG, &LoggingWindow::OnLogMessage, this);
|
||||
|
||||
std::unique_lock lock(s_mutex);
|
||||
cemu_assert_debug(s_instance == nullptr);
|
||||
s_instance = this;
|
||||
}
|
||||
|
||||
LoggingWindow::~LoggingWindow()
|
||||
{
|
||||
this->Unbind(EVT_LOG, &LoggingWindow::OnLogMessage, this);
|
||||
|
||||
std::unique_lock lock(s_mutex);
|
||||
s_instance = nullptr;
|
||||
}
|
||||
|
||||
void LoggingWindow::Log(std::string_view filter, std::string_view message)
|
||||
{
|
||||
std::shared_lock lock(s_mutex);
|
||||
if(!s_instance)
|
||||
return;
|
||||
|
||||
wxLogEvent event(std::string {filter}, std::string{ message });
|
||||
s_instance->OnLogMessage(event);
|
||||
|
||||
//const auto log_event = new wxLogEvent(filter, message);
|
||||
//wxQueueEvent(s_instance, log_event);
|
||||
}
|
||||
|
||||
void LoggingWindow::Log(std::string_view filter, std::wstring_view message)
|
||||
{
|
||||
std::shared_lock lock(s_mutex);
|
||||
if(!s_instance)
|
||||
return;
|
||||
|
||||
wxLogEvent event(std::string {filter}, std::wstring{ message });
|
||||
s_instance->OnLogMessage(event);
|
||||
|
||||
//const auto log_event = new wxLogEvent(filter, message);
|
||||
//wxQueueEvent(s_instance, log_event);
|
||||
}
|
||||
|
||||
void LoggingWindow::OnLogMessage(wxLogEvent& event)
|
||||
{
|
||||
m_log_list->PushEntry(event.GetFilter(), event.GetMessage());
|
||||
}
|
||||
|
||||
void LoggingWindow::OnFilterChange(wxCommandEvent& event)
|
||||
{
|
||||
m_log_list->SetActiveFilter(from_wxString(m_filter->GetValue()));
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void LoggingWindow::OnFilterMessageChange(wxCommandEvent& event)
|
||||
{
|
||||
m_log_list->SetFilterMessage(m_filter_message->GetValue());
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
44
src/gui/LoggingWindow.h
Normal file
44
src/gui/LoggingWindow.h
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
#include <wx/frame.h>
|
||||
#include <wx/listbox.h>
|
||||
#include <wx/combobox.h>
|
||||
#include "gui/components/wxLogCtrl.h"
|
||||
|
||||
class wxLogEvent;
|
||||
|
||||
class LoggingWindow : public wxFrame
|
||||
{
|
||||
public:
|
||||
LoggingWindow(wxFrame* parent);
|
||||
~LoggingWindow();
|
||||
|
||||
static void Log(std::string_view filter, std::string_view message);
|
||||
static void Log(std::string_view message) { Log("", message); }
|
||||
static void Log(std::string_view filter, std::wstring_view message);
|
||||
static void Log(std::wstring_view message){ Log("", message); }
|
||||
|
||||
template<typename ...TArgs>
|
||||
static void Log(std::string_view filter, std::string_view format, TArgs&&... args)
|
||||
{
|
||||
Log(filter, fmt::format(format, std::forward<TArgs>(args)...));
|
||||
}
|
||||
|
||||
template<typename ...TArgs>
|
||||
static void Log(std::string_view filter, std::wstring_view format, TArgs&&... args)
|
||||
{
|
||||
Log(filter, fmt::format(format, std::forward<TArgs>(args)...));
|
||||
}
|
||||
private:
|
||||
void OnLogMessage(wxLogEvent& event);
|
||||
void OnFilterChange(wxCommandEvent& event);
|
||||
void OnFilterMessageChange(wxCommandEvent& event);
|
||||
|
||||
wxComboBox* m_filter;
|
||||
wxLogCtrl* m_log_list;
|
||||
wxCheckBox* m_filter_message;
|
||||
|
||||
inline static std::shared_mutex s_mutex;
|
||||
inline static LoggingWindow* s_instance = nullptr;
|
||||
};
|
||||
|
||||
2400
src/gui/MainWindow.cpp
Normal file
2400
src/gui/MainWindow.cpp
Normal file
File diff suppressed because it is too large
Load diff
236
src/gui/MainWindow.h
Normal file
236
src/gui/MainWindow.h
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
#pragma once
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <wx/dataview.h>
|
||||
#include <wx/infobar.h>
|
||||
#include "wxcomponents/checkedlistctrl.h"
|
||||
|
||||
#include "gui/PadViewFrame.h"
|
||||
#include "gui/MemorySearcherTool.h"
|
||||
|
||||
#include "config/XMLConfig.h"
|
||||
|
||||
#include "gui/LoggingWindow.h"
|
||||
#include "gui/components/wxGameList.h"
|
||||
|
||||
#include <future>
|
||||
|
||||
class DebuggerWindow2;
|
||||
struct GameEntry;
|
||||
class DiscordPresence;
|
||||
class TitleManager;
|
||||
class wxLaunchGameEvent;
|
||||
|
||||
wxDECLARE_EVENT(wxEVT_LAUNCH_GAME, wxLaunchGameEvent);
|
||||
wxDECLARE_EVENT(wxEVT_SET_WINDOW_TITLE, wxCommandEvent);
|
||||
|
||||
class wxLaunchGameEvent : public wxCommandEvent
|
||||
{
|
||||
public:
|
||||
enum class INITIATED_BY
|
||||
{
|
||||
MENU, // via file menu
|
||||
DRAG_AND_DROP,
|
||||
GAME_LIST,
|
||||
TITLE_MANAGER,
|
||||
COMMAND_LINE, // -g parameter
|
||||
};
|
||||
|
||||
wxLaunchGameEvent(fs::path path, INITIATED_BY initiatedBy)
|
||||
: wxCommandEvent(wxEVT_LAUNCH_GAME), m_launchPath(path), m_initiatedBy(initiatedBy) {}
|
||||
|
||||
[[nodiscard]] fs::path GetPath() const { return m_launchPath; }
|
||||
[[nodiscard]] INITIATED_BY GetInitiatedBy() const { return m_initiatedBy; }
|
||||
|
||||
wxEvent* Clone() const { return new wxLaunchGameEvent(*this); }
|
||||
|
||||
private:
|
||||
fs::path m_launchPath;
|
||||
INITIATED_BY m_initiatedBy;
|
||||
};
|
||||
|
||||
class MainWindow : public wxFrame
|
||||
{
|
||||
friend class CemuApp;
|
||||
|
||||
public:
|
||||
MainWindow();
|
||||
~MainWindow();
|
||||
|
||||
void UpdateSettingsAfterGameLaunch();
|
||||
void RestoreSettingsAfterGameExited();
|
||||
|
||||
bool FileLoad(std::wstring fileName, wxLaunchGameEvent::INITIATED_BY initiatedBy);
|
||||
|
||||
[[nodiscard]] bool IsGameLaunched() const { return m_game_launched; }
|
||||
|
||||
void SetFullScreen(bool state);
|
||||
void SetMenuVisible(bool state);
|
||||
void UpdateNFCMenu();
|
||||
bool IsMenuHidden() const;
|
||||
void TogglePadView();
|
||||
|
||||
#if BOOST_OS_WINDOWS
|
||||
WXLRESULT MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) override;
|
||||
#endif
|
||||
void OpenSettings();
|
||||
|
||||
PadViewFrame* GetPadView() const { return m_padView; }
|
||||
|
||||
void OnSizeEvent(wxSizeEvent& event);
|
||||
void OnMove(wxMoveEvent& event);
|
||||
|
||||
void OnDebuggerClose(wxCloseEvent& event);
|
||||
void OnPadClose(wxCloseEvent& event);
|
||||
void OnMemorySearcherClose(wxCloseEvent& event);
|
||||
void OnMouseWheel(wxMouseEvent& event);
|
||||
void OnClose(wxCloseEvent& event);
|
||||
void OnFileMenu(wxCommandEvent& event);
|
||||
void OnLaunchFromFile(wxLaunchGameEvent& event);
|
||||
void OnInstallUpdate(wxCommandEvent& event);
|
||||
void OnFileExit(wxCommandEvent& event);
|
||||
void OnNFCMenu(wxCommandEvent& event);
|
||||
void OnOptionsInput(wxCommandEvent& event);
|
||||
void OnAccountSelect(wxCommandEvent& event);
|
||||
void OnConsoleLanguage(wxCommandEvent& event);
|
||||
void OnHelpVistWebpage(wxCommandEvent& event);
|
||||
void OnHelpAbout(wxCommandEvent& event);
|
||||
void OnHelpGettingStarted(wxCommandEvent& event);
|
||||
void OnHelpUpdate(wxCommandEvent& event);
|
||||
void OnAfterCallShowErrorDialog();
|
||||
void OnDebugSetting(wxCommandEvent& event);
|
||||
void OnDebugLoggingToggleFlagGeneric(wxCommandEvent& event);
|
||||
void OnPPCInfoToggle(wxCommandEvent& event);
|
||||
void OnDebugDumpUsedTextures(wxCommandEvent& event);
|
||||
void OnDebugDumpUsedShaders(wxCommandEvent& event);
|
||||
void OnLoggingWindow(wxCommandEvent& event);
|
||||
void OnDebugViewPPCThreads(wxCommandEvent& event);
|
||||
void OnDebugViewPPCDebugger(wxCommandEvent& event);
|
||||
void OnDebugViewAudioDebugger(wxCommandEvent& event);
|
||||
void OnDebugViewTextureRelations(wxCommandEvent& event);
|
||||
void OnMouseMove(wxMouseEvent& event);
|
||||
void OnMouseLeft(wxMouseEvent& event);
|
||||
void OnMouseRight(wxMouseEvent& event);
|
||||
void OnGameListBeginUpdate(wxCommandEvent& event);
|
||||
void OnGameListEndUpdate(wxCommandEvent& event);
|
||||
void OnAccountListRefresh(wxCommandEvent& event);
|
||||
void OnRequestGameListRefresh(wxCommandEvent& event);
|
||||
void OnSetWindowTitle(wxCommandEvent& event);
|
||||
|
||||
void OnKeyUp(wxKeyEvent& event);
|
||||
void OnChar(wxKeyEvent& event);
|
||||
|
||||
void OnToolsInput(wxCommandEvent& event);
|
||||
void OnGesturePan(wxPanGestureEvent& event);
|
||||
|
||||
void OnGameLoaded();
|
||||
|
||||
void AsyncSetTitle(std::string_view windowTitle);
|
||||
|
||||
void CreateCanvas();
|
||||
void DestroyCanvas();
|
||||
|
||||
std::wstring GetGameName(std::wstring_view file_name);
|
||||
static void ShowCursor(bool state);
|
||||
|
||||
uintptr_t GetRenderCanvasHWND();
|
||||
|
||||
static void RequestGameListRefresh();
|
||||
static void RequestLaunchGame(fs::path filePath, wxLaunchGameEvent::INITIATED_BY initiatedBy);
|
||||
|
||||
private:
|
||||
void RecreateMenu();
|
||||
static wxString GetInitialWindowTitle();
|
||||
void ShowGettingStartedDialog();
|
||||
bool EnableOnlineMode() const;
|
||||
|
||||
bool InstallUpdate(const fs::path& metaFilePath);
|
||||
|
||||
void OnTimer(wxTimerEvent& event);
|
||||
|
||||
wxRect GetDesktopRect();
|
||||
|
||||
MemorySearcherTool* m_toolWindow = nullptr;
|
||||
TitleManager* m_title_manager = nullptr;
|
||||
PadViewFrame* m_padView = nullptr;
|
||||
wxWindow* m_graphic_pack_window = nullptr;
|
||||
|
||||
wxTimer* m_timer;
|
||||
wxPoint m_mouse_position{};
|
||||
std::chrono::steady_clock::time_point m_last_mouse_move_time;
|
||||
wxSize m_restored_size;
|
||||
wxPoint m_restored_position;
|
||||
|
||||
wxStatusBar* m_statusBar{};
|
||||
|
||||
bool m_menu_visible = false;
|
||||
bool m_game_launched = false;
|
||||
|
||||
#ifdef ENABLE_DISCORD_RPC
|
||||
std::unique_ptr<DiscordPresence> m_discord;
|
||||
#endif
|
||||
|
||||
std::string m_launched_game_name;
|
||||
|
||||
DebuggerWindow2* m_debugger_window = nullptr;
|
||||
LoggingWindow* m_logging_window = nullptr;
|
||||
|
||||
std::future<bool> m_update_available;
|
||||
|
||||
wxMenuItem* m_check_update_menu{};
|
||||
void LoadSettings();
|
||||
void SaveSettings();
|
||||
|
||||
std::string GetRegionString(uint32 region) const;
|
||||
|
||||
void OnGraphicWindowClose(wxCloseEvent& event);
|
||||
void OnGraphicWindowOpen(wxTitleIdEvent& event);
|
||||
|
||||
// panels
|
||||
wxPanel* m_main_panel{}, * m_game_panel{};
|
||||
|
||||
// rendering
|
||||
wxWindow* m_render_canvas{};
|
||||
|
||||
// gamelist
|
||||
wxGameList* m_game_list;
|
||||
wxInfoBar* m_info_bar;
|
||||
|
||||
// menu
|
||||
wxMenuBar* m_menuBar = nullptr;
|
||||
|
||||
// file
|
||||
wxMenu* m_fileMenu;
|
||||
wxMenuItem* m_fileMenuSeparator0;
|
||||
wxMenuItem* m_fileMenuSeparator1;
|
||||
wxMenuItem* m_loadMenuItem;
|
||||
wxMenuItem* m_installUpdateMenuItem;
|
||||
wxMenuItem* m_exitMenuItem;
|
||||
|
||||
// options
|
||||
//wxMenu* m_gpuBufferCacheAccuracySubmenu;
|
||||
wxMenu* m_optionsAccountMenu;
|
||||
|
||||
wxMenuItem* m_fullscreenMenuItem;
|
||||
wxMenuItem* m_padViewMenuItem;
|
||||
|
||||
// tools
|
||||
wxMenuItem* m_memorySearcherMenuItem;
|
||||
|
||||
// cpu
|
||||
//wxMenu* m_cpuModeSubmenu;
|
||||
wxMenu* m_cpuTimerSubmenu;
|
||||
|
||||
// nfc
|
||||
wxMenu* m_nfcMenu;
|
||||
wxMenuItem* m_nfcMenuSeparator0;
|
||||
|
||||
// debug
|
||||
wxMenu* m_debugMenu;
|
||||
wxMenu* m_loggingSubmenu;
|
||||
wxMenuItem* m_asyncCompile;
|
||||
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
extern MainWindow* g_mainFrame;
|
||||
777
src/gui/MemorySearcherTool.cpp
Normal file
777
src/gui/MemorySearcherTool.cpp
Normal file
|
|
@ -0,0 +1,777 @@
|
|||
#include "gui/wxgui.h"
|
||||
|
||||
#include "gui/MemorySearcherTool.h"
|
||||
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
#include "config/ActiveSettings.h"
|
||||
#include "gui/helpers/wxHelpers.h"
|
||||
#include "Common/filestream.h"
|
||||
#include "util/IniParser/IniParser.h"
|
||||
#include "util/helpers/StringHelpers.h"
|
||||
#include "Cafe/CafeSystem.h"
|
||||
|
||||
enum
|
||||
{
|
||||
COMBOBOX_DATATYPE = wxID_HIGHEST + 1,
|
||||
TEXT_VALUE,
|
||||
BUTTON_START,
|
||||
BUTTON_FILTER,
|
||||
LIST_RESULTS,
|
||||
LIST_ENTRYTABLE,
|
||||
TIMER_REFRESH,
|
||||
LIST_ENTRY_ADD,
|
||||
LIST_ENTRY_REMOVE,
|
||||
};
|
||||
|
||||
wxDEFINE_EVENT(wxEVT_SEARCH_FINISHED, wxCommandEvent);
|
||||
|
||||
wxBEGIN_EVENT_TABLE(MemorySearcherTool, wxFrame)
|
||||
EVT_CLOSE(MemorySearcherTool::OnClose)
|
||||
EVT_BUTTON(BUTTON_START, MemorySearcherTool::OnSearch)
|
||||
EVT_BUTTON(BUTTON_FILTER, MemorySearcherTool::OnFilter)
|
||||
EVT_TIMER(TIMER_REFRESH, MemorySearcherTool::OnTimerTick)
|
||||
wxEND_EVENT_TABLE()
|
||||
|
||||
constexpr auto kMaxResultCount = 5000;
|
||||
|
||||
const wxString kDatatypeFloat = "float";
|
||||
const wxString kDatatypeDouble = "double";
|
||||
const wxString kDatatypeString = "string";
|
||||
const wxString kDatatypeInt8 = "int8";
|
||||
const wxString kDatatypeInt16 = "int16";
|
||||
const wxString kDatatypeInt32 = "int32";
|
||||
const wxString kDatatypeInt64 = "int64";
|
||||
const wxString kDataTypeNames[] = {kDatatypeFloat,kDatatypeDouble,/*DATATYPE_STRING,*/kDatatypeInt8,kDatatypeInt16,kDatatypeInt32,kDatatypeInt64};
|
||||
|
||||
MemorySearcherTool::MemorySearcherTool(wxFrame* parent)
|
||||
: wxFrame(parent, wxID_ANY, _("Memory Searcher"), wxDefaultPosition, wxSize(600, 540), wxDEFAULT_FRAME_STYLE | wxTAB_TRAVERSAL)
|
||||
{
|
||||
this->SetSizeHints(wxDefaultSize, wxDefaultSize);
|
||||
this->wxWindowBase::SetBackgroundColour(*wxWHITE);
|
||||
this->wxTopLevelWindowBase::SetMinSize(wxSize(600, 540));
|
||||
|
||||
auto* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto* row1 = new wxFlexGridSizer(0, 4, 0, 0);
|
||||
row1->AddGrowableCol(1);
|
||||
|
||||
m_cbDataType = new wxComboBox(this, COMBOBOX_DATATYPE, kDatatypeFloat, wxDefaultPosition, wxDefaultSize, std::size(kDataTypeNames), kDataTypeNames, wxCB_READONLY);
|
||||
m_textValue = new wxTextCtrl(this, TEXT_VALUE);
|
||||
m_buttonStart = new wxButton(this, BUTTON_START, _("Search"));
|
||||
m_buttonFilter = new wxButton(this, BUTTON_FILTER, _("Filter"));
|
||||
m_buttonFilter->Disable();
|
||||
|
||||
row1->Add(m_cbDataType, 0, wxALL, 5);
|
||||
row1->Add(m_textValue, 0, wxALL | wxEXPAND, 5);
|
||||
row1->Add(m_buttonStart, 0, wxALL, 5);
|
||||
row1->Add(m_buttonFilter, 0, wxALL, 5);
|
||||
|
||||
sizer->Add(row1, 0, wxEXPAND, 5);
|
||||
|
||||
auto* row2 = new wxFlexGridSizer(0, 1, 0, 0);
|
||||
row2->AddGrowableCol(0);
|
||||
|
||||
m_gauge = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL);
|
||||
m_gauge->SetValue(0);
|
||||
m_gauge->Enable(false);
|
||||
|
||||
m_textEntryTable = new wxStaticText(this, wxID_ANY, _("Results"));
|
||||
m_listResults = new wxListCtrl(this, LIST_RESULTS, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SORT_ASCENDING);
|
||||
m_listResults->Bind(wxEVT_LEFT_DCLICK, &MemorySearcherTool::OnResultListClick, this);
|
||||
{
|
||||
wxListItem col0;
|
||||
col0.SetId(0);
|
||||
col0.SetText(_("address"));
|
||||
col0.SetWidth(100);
|
||||
m_listResults->InsertColumn(0, col0);
|
||||
wxListItem col1;
|
||||
col1.SetId(1);
|
||||
col1.SetText(_("value"));
|
||||
col1.SetWidth(250);
|
||||
m_listResults->InsertColumn(1, col1);
|
||||
}
|
||||
|
||||
auto textEntryTable = new wxStaticText(this, wxID_ANY, _("Stored Entries"));
|
||||
m_listEntryTable = new wxDataViewListCtrl(this, LIST_ENTRYTABLE, wxDefaultPosition, wxSize(420, 200), wxDV_HORIZ_RULES);
|
||||
m_listEntryTable->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, &MemorySearcherTool::OnEntryListRightClick, this);
|
||||
m_listEntryTable->Bind(wxEVT_COMMAND_DATAVIEW_ITEM_EDITING_DONE, &MemorySearcherTool::OnItemEdited, this);
|
||||
{
|
||||
m_listEntryTable->AppendTextColumn(_("description"), wxDATAVIEW_CELL_EDITABLE, 150, wxALIGN_LEFT, wxDATAVIEW_COL_SORTABLE);
|
||||
m_listEntryTable->AppendTextColumn(_("address"), wxDATAVIEW_CELL_INERT, 100, wxALIGN_LEFT, wxDATAVIEW_COL_SORTABLE);
|
||||
m_listEntryTable->AppendTextColumn(_("type"));
|
||||
m_listEntryTable->AppendTextColumn(_("value"), wxDATAVIEW_CELL_EDITABLE);
|
||||
m_listEntryTable->AppendToggleColumn(_("freeze"), wxDATAVIEW_CELL_ACTIVATABLE, 50, wxALIGN_LEFT, 0);
|
||||
}
|
||||
|
||||
row2->AddGrowableRow(3);
|
||||
row2->AddGrowableRow(5);
|
||||
|
||||
row2->Add(m_gauge, 0, wxALL | wxEXPAND, 5);
|
||||
row2->Add(0, 10, 1, wxEXPAND, 5);
|
||||
row2->Add(m_textEntryTable, 0, wxALL, 5);
|
||||
row2->Add(m_listResults, 1, wxALL | wxEXPAND, 5);
|
||||
row2->Add(textEntryTable, 0, wxALL, 5);
|
||||
row2->Add(m_listEntryTable, 1, wxALL | wxEXPAND, 5);
|
||||
|
||||
sizer->Add(row2, 1, wxEXPAND, 5);
|
||||
|
||||
// load stored entries
|
||||
Load();
|
||||
|
||||
this->Bind(wxEVT_SEARCH_FINISHED, &MemorySearcherTool::OnSearchFinished, this);
|
||||
this->Bind(wxEVT_SET_GAUGE_VALUE, &MemorySearcherTool::OnUpdateGauge, this);
|
||||
|
||||
m_refresh_timer = new wxTimer(this, TIMER_REFRESH);
|
||||
m_refresh_timer->Start(250);
|
||||
|
||||
this->SetSizer(sizer);
|
||||
this->wxWindowBase::Layout();
|
||||
|
||||
this->Centre(wxBOTH);
|
||||
}
|
||||
|
||||
MemorySearcherTool::~MemorySearcherTool()
|
||||
{
|
||||
m_refresh_timer->Stop();
|
||||
|
||||
m_running = false;
|
||||
if (m_worker.joinable())
|
||||
m_worker.join();
|
||||
}
|
||||
|
||||
void MemorySearcherTool::OnTimerTick(wxTimerEvent& event)
|
||||
{
|
||||
RefreshResultList();
|
||||
RefreshStashList();
|
||||
}
|
||||
|
||||
void MemorySearcherTool::OnClose(wxCloseEvent& event)
|
||||
{
|
||||
Save();
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void MemorySearcherTool::OnSearchFinished(wxCommandEvent&)
|
||||
{
|
||||
FillResultList();
|
||||
m_search_running = false;
|
||||
m_buttonStart->Enable();
|
||||
m_buttonFilter->Enable();
|
||||
}
|
||||
|
||||
void MemorySearcherTool::OnUpdateGauge(wxSetGaugeValue& event)
|
||||
{
|
||||
auto* gauge = event.GetGauge();
|
||||
const auto value = event.GetValue();
|
||||
if (event.GetRange() != 0)
|
||||
{
|
||||
gauge->SetRange(event.GetRange());
|
||||
gauge->SetValue(value);
|
||||
return;
|
||||
}
|
||||
|
||||
debug_printf("update gauge: %d + %d = %d (/%d)\n", gauge->GetValue(), value, gauge->GetValue() + value, gauge->GetRange());
|
||||
gauge->SetValue(gauge->GetValue() + value);
|
||||
}
|
||||
|
||||
void MemorySearcherTool::OnSearch(wxCommandEvent&)
|
||||
{
|
||||
if (m_clear_state)
|
||||
{
|
||||
Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_search_running)
|
||||
return;
|
||||
|
||||
if (m_textValue->IsEmpty())
|
||||
return;
|
||||
|
||||
SetSearchDataType();
|
||||
|
||||
if (!VerifySearchValue())
|
||||
{
|
||||
wxMessageBox(_("Your entered value is not valid for the selected datatype."), _("Error"), wxICON_ERROR);
|
||||
return;
|
||||
}
|
||||
m_buttonStart->Disable();
|
||||
m_buttonFilter->Disable();
|
||||
m_cbDataType->Disable();
|
||||
m_buttonStart->SetLabelText(_("Clear"));
|
||||
m_clear_state = true;
|
||||
|
||||
if (m_worker.joinable())
|
||||
m_worker.join();
|
||||
|
||||
m_worker = std::thread([this]()
|
||||
{
|
||||
m_search_jobs.clear();
|
||||
uint32 total_size = 0;
|
||||
for (const auto& itr : memory_getMMURanges())
|
||||
{
|
||||
if (!m_running)
|
||||
return;
|
||||
|
||||
if (!itr->isMapped())
|
||||
continue;
|
||||
|
||||
void* ptr = itr->getPtr();
|
||||
const uint32 size = itr->getSize();
|
||||
|
||||
total_size += (size / kGaugeStep);
|
||||
m_search_jobs.emplace_back(std::async(std::launch::async, [this, ptr, size]() { return SearchValues(m_searchDataType, ptr, size); }));
|
||||
}
|
||||
|
||||
wxQueueEvent(this, new wxSetGaugeValue(0, total_size, m_gauge));
|
||||
|
||||
ListType_t tmp;
|
||||
for (auto& it : m_search_jobs)
|
||||
{
|
||||
const auto result = it.get();
|
||||
tmp.insert(tmp.end(), result.cbegin(), result.cend());
|
||||
}
|
||||
|
||||
std::unique_lock lock(m_mutex);
|
||||
m_searchBuffer.swap(tmp);
|
||||
lock.unlock();
|
||||
|
||||
wxQueueEvent(this, new wxCommandEvent(wxEVT_SEARCH_FINISHED));
|
||||
});
|
||||
}
|
||||
|
||||
void MemorySearcherTool::OnFilter(wxCommandEvent& event)
|
||||
{
|
||||
m_buttonStart->Disable();
|
||||
m_buttonFilter->Disable();
|
||||
|
||||
if (m_worker.joinable())
|
||||
m_worker.join();
|
||||
|
||||
m_worker = std::thread([this]()
|
||||
{
|
||||
const auto count = (uint32)m_searchBuffer.size();
|
||||
wxQueueEvent(this, new wxSetGaugeValue(0, count, m_gauge));
|
||||
|
||||
auto tmp = FilterValues(m_searchDataType);
|
||||
|
||||
std::unique_lock lock(m_mutex);
|
||||
m_searchBuffer.swap(tmp);
|
||||
lock.unlock();
|
||||
|
||||
wxQueueEvent(this, new wxCommandEvent(wxEVT_SEARCH_FINISHED));
|
||||
});
|
||||
|
||||
m_gauge->SetValue(0);
|
||||
}
|
||||
|
||||
void MemorySearcherTool::Load()
|
||||
{
|
||||
const auto memorySearcherPath = ActiveSettings::GetPath("memorySearcher/{:016x}.ini", CafeSystem::GetForegroundTitleId());
|
||||
auto memSearcherIniContents = FileStream::LoadIntoMemory(memorySearcherPath);
|
||||
if (!memSearcherIniContents)
|
||||
return;
|
||||
|
||||
IniParser iniParser(*memSearcherIniContents, _utf8Wrapper(memorySearcherPath));
|
||||
while (iniParser.NextSection())
|
||||
{
|
||||
auto option_description = iniParser.FindOption("description");
|
||||
auto option_address = iniParser.FindOption("address");
|
||||
auto option_type = iniParser.FindOption("type");
|
||||
auto option_value = iniParser.FindOption("value");
|
||||
|
||||
if (!option_description || !option_address || !option_type || !option_value)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
const auto addr = StringHelpers::ToInt64(*option_address);
|
||||
if (!IsAddressValid(addr))
|
||||
continue;
|
||||
}
|
||||
catch (const std::invalid_argument&)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
for (const auto& entry : kDataTypeNames)
|
||||
{
|
||||
if (boost::iequals(entry, *option_type))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found && !boost::iequals(kDatatypeString, *option_type))
|
||||
continue;
|
||||
|
||||
wxVector<wxVariant> data;
|
||||
data.push_back(std::string(*option_description).c_str());
|
||||
data.push_back(std::string(*option_address).c_str());
|
||||
data.push_back(std::string(*option_type).c_str());
|
||||
data.push_back(std::string(*option_value).c_str());
|
||||
data.push_back(!option_value->empty());
|
||||
m_listEntryTable->AppendItem(data);
|
||||
}
|
||||
}
|
||||
|
||||
void MemorySearcherTool::Save()
|
||||
{
|
||||
const auto memorySearcherPath = ActiveSettings::GetPath("memorySearcher/{:016x}.ini", CafeSystem::GetForegroundTitleId());
|
||||
FileStream* fs = FileStream::createFile2(memorySearcherPath);
|
||||
if (fs)
|
||||
{
|
||||
for (int i = 0; i < m_listEntryTable->GetItemCount(); ++i)
|
||||
{
|
||||
fs->writeLine("[Entry]");
|
||||
|
||||
std::string tmp = "description=" + std::string(m_listEntryTable->GetTextValue(i, 0).mbc_str());
|
||||
fs->writeLine(tmp.c_str());
|
||||
|
||||
tmp = "address=" + std::string(m_listEntryTable->GetTextValue(i, 1).mbc_str());
|
||||
fs->writeLine(tmp.c_str());
|
||||
|
||||
tmp = "type=" + std::string(m_listEntryTable->GetTextValue(i, 2).mbc_str());
|
||||
fs->writeLine(tmp.c_str());
|
||||
|
||||
// only save value when FREEZE is active
|
||||
if (m_listEntryTable->GetToggleValue(i, 4))
|
||||
{
|
||||
tmp = "value=" + std::string(m_listEntryTable->GetTextValue(i, 3).mbc_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
tmp = "value=";
|
||||
}
|
||||
fs->writeLine(tmp.c_str());
|
||||
|
||||
fs->writeLine("");
|
||||
}
|
||||
delete fs;
|
||||
}
|
||||
}
|
||||
|
||||
bool MemorySearcherTool::IsAddressValid(uint32 addr)
|
||||
{
|
||||
for (const auto& itr : memory_getMMURanges())
|
||||
{
|
||||
if (!itr->isMapped())
|
||||
continue;
|
||||
|
||||
void* ptr = itr->getPtr();
|
||||
const uint32 size = itr->getSize();
|
||||
|
||||
MEMPTR start((uint8*)ptr);
|
||||
MEMPTR end((uint8*)ptr + size);
|
||||
if (start.GetMPTR() <= addr && addr < end.GetMPTR())
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MemorySearcherTool::OnEntryListRightClick(wxDataViewEvent& event)
|
||||
{
|
||||
//void *data = reinterpret_cast<void *>(event.GetItem().GetData());
|
||||
wxMenu mnu;
|
||||
//mnu.SetClientData(data);
|
||||
mnu.Append(LIST_ENTRY_ADD, _("&Add new entry"))->Enable(false);
|
||||
mnu.Append(LIST_ENTRY_REMOVE, _("&Remove entry"));
|
||||
mnu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MemorySearcherTool::OnPopupClick), nullptr, this);
|
||||
PopupMenu(&mnu);
|
||||
}
|
||||
|
||||
void MemorySearcherTool::OnResultListClick(wxMouseEvent& event)
|
||||
{
|
||||
long selectedIndex = -1;
|
||||
|
||||
while (true)
|
||||
{
|
||||
selectedIndex = m_listResults->GetNextItem(selectedIndex, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
|
||||
if (selectedIndex == -1)
|
||||
break;
|
||||
|
||||
long address = m_listResults->GetItemData(selectedIndex);
|
||||
auto currValue = m_listResults->GetItemText(selectedIndex, 1);
|
||||
|
||||
char addressString[256];
|
||||
sprintf(addressString, "0x%08x", address);
|
||||
|
||||
// description, address, type, value, freeze
|
||||
wxVector<wxVariant> data;
|
||||
data.push_back("");
|
||||
data.push_back(addressString);
|
||||
data.push_back(m_cbDataType->GetValue());
|
||||
data.push_back(currValue);
|
||||
data.push_back(false);
|
||||
m_listEntryTable->AppendItem(data);
|
||||
}
|
||||
}
|
||||
|
||||
void MemorySearcherTool::Reset()
|
||||
{
|
||||
m_searchBuffer.clear();
|
||||
m_buttonStart->SetLabelText(_("Search"));
|
||||
m_textEntryTable->SetLabelText(_("Results"));
|
||||
m_buttonFilter->Disable();
|
||||
m_cbDataType->Enable();
|
||||
m_textValue->SetValue("");
|
||||
m_listResults->DeleteAllItems();
|
||||
m_clear_state = false;
|
||||
}
|
||||
|
||||
bool MemorySearcherTool::VerifySearchValue() const
|
||||
{
|
||||
const auto s1 = m_textValue->GetValue();
|
||||
const auto s2 = s1.c_str();
|
||||
const auto inputString = s2.AsChar();
|
||||
|
||||
switch (m_searchDataType)
|
||||
{
|
||||
case SearchDataType_String:
|
||||
return true;
|
||||
case SearchDataType_Float:
|
||||
{
|
||||
float value;
|
||||
return ConvertStringToType(inputString, value);
|
||||
}
|
||||
case SearchDataType_Double:
|
||||
{
|
||||
double value;
|
||||
return ConvertStringToType(inputString, value);
|
||||
}
|
||||
case SearchDataType_Int8:
|
||||
{
|
||||
sint8 value;
|
||||
return ConvertStringToType(inputString, value);
|
||||
}
|
||||
case SearchDataType_Int16:
|
||||
{
|
||||
sint16 value;
|
||||
return ConvertStringToType(inputString, value);
|
||||
}
|
||||
case SearchDataType_Int32:
|
||||
{
|
||||
sint32 value;
|
||||
return ConvertStringToType(inputString, value);
|
||||
}
|
||||
case SearchDataType_Int64:
|
||||
{
|
||||
sint64 value;
|
||||
return ConvertStringToType(inputString, value);
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void MemorySearcherTool::FillResultList()
|
||||
{
|
||||
//char text[128];
|
||||
//sprintf(text, "Results (%u)", (uint32)m_searchBuffer.size());
|
||||
auto text = wxStringFormat(_("Results ({0})"), L"%llu", m_searchBuffer.size());
|
||||
m_textEntryTable->SetLabelText(text);
|
||||
|
||||
m_listResults->DeleteAllItems();
|
||||
|
||||
if (m_searchBuffer.empty() || m_searchBuffer.size() > kMaxResultCount)
|
||||
return;
|
||||
|
||||
for (const auto& address : m_searchBuffer)
|
||||
{
|
||||
const auto index = m_listResults->InsertItem(0, fmt::format("{:#08x}", address.GetMPTR()));
|
||||
m_listResults->SetItemData(index, address.GetMPTR());
|
||||
m_listResults->SetItem(index, 1, m_textValue->GetValue());
|
||||
}
|
||||
}
|
||||
|
||||
void MemorySearcherTool::RefreshResultList()
|
||||
{
|
||||
std::unique_lock lock(m_mutex);
|
||||
if (m_searchBuffer.empty() || m_searchBuffer.size() > kMaxResultCount)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < m_listResults->GetItemCount(); ++i)
|
||||
{
|
||||
const auto addr = m_listResults->GetItemData(i);
|
||||
switch (m_searchDataType)
|
||||
{
|
||||
case SearchDataType_String:
|
||||
{
|
||||
// TODO Peter
|
||||
break;
|
||||
}
|
||||
case SearchDataType_Float:
|
||||
{
|
||||
const auto value = memory_read<float>(addr);
|
||||
m_listResults->SetItem(i, 1, fmt::format("{}", value));
|
||||
break;
|
||||
}
|
||||
case SearchDataType_Double:
|
||||
{
|
||||
const auto value = memory_read<double>(addr);
|
||||
m_listResults->SetItem(i, 1, fmt::format("{}", value));
|
||||
break;
|
||||
}
|
||||
case SearchDataType_Int8:
|
||||
{
|
||||
const auto value = memory_read<sint8>(addr);
|
||||
m_listResults->SetItem(i, 1, fmt::format("{}", value));
|
||||
break;
|
||||
}
|
||||
case SearchDataType_Int16:
|
||||
{
|
||||
const auto value = memory_read<sint16>(addr);
|
||||
m_listResults->SetItem(i, 1, fmt::format("{}", value));
|
||||
break;
|
||||
}
|
||||
case SearchDataType_Int32:
|
||||
{
|
||||
const auto value = memory_read<sint32>(addr);
|
||||
m_listResults->SetItem(i, 1, fmt::format("{}", value));
|
||||
break;
|
||||
}
|
||||
case SearchDataType_Int64:
|
||||
{
|
||||
const auto value = memory_read<sint64>(addr);
|
||||
m_listResults->SetItem(i, 1, fmt::format("{}", value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MemorySearcherTool::RefreshStashList()
|
||||
{
|
||||
for (int i = 0; i < m_listEntryTable->GetItemCount(); ++i)
|
||||
{
|
||||
auto freeze = m_listEntryTable->GetToggleValue(i, 4);
|
||||
|
||||
auto addressText = std::string(m_listEntryTable->GetTextValue(i, 1).mbc_str());
|
||||
auto type = std::string(m_listEntryTable->GetTextValue(i, 2).mbc_str());
|
||||
|
||||
auto address = stol(addressText, nullptr, 16);
|
||||
|
||||
// if freeze is activated, set the value instead of refreshing it
|
||||
if (freeze)
|
||||
{
|
||||
wxVariant var;
|
||||
m_listEntryTable->GetValue(var, i, 3);
|
||||
if (type == kDatatypeFloat)
|
||||
{
|
||||
auto value = (float)var.GetDouble();
|
||||
memory_writeFloat(address, value);
|
||||
}
|
||||
else if (type == kDatatypeDouble)
|
||||
{
|
||||
auto value = var.GetDouble();
|
||||
memory_writeDouble(address, value);
|
||||
}
|
||||
else if (type == kDatatypeInt8)
|
||||
{
|
||||
auto value = var.GetInteger();
|
||||
memory_writeU8(address, value);
|
||||
}
|
||||
else if (type == kDatatypeInt16)
|
||||
{
|
||||
auto value = var.GetInteger();
|
||||
memory_writeU16(address, value);
|
||||
}
|
||||
else if (type == kDatatypeInt32)
|
||||
{
|
||||
auto value = var.GetInteger();
|
||||
memory_writeU32(address, value);
|
||||
}
|
||||
else if (type == kDatatypeInt64)
|
||||
{
|
||||
auto valueText = std::string(var.GetString().mbc_str());
|
||||
auto value = stoull(valueText);
|
||||
memory_writeU64(address, value);
|
||||
}
|
||||
else if (type == kDatatypeString)
|
||||
{
|
||||
auto valueText = std::string(var.GetString().mbc_str());
|
||||
for (int i = 0; i < valueText.size(); ++i)
|
||||
{
|
||||
memory_writeU8(address + i, valueText[i]);
|
||||
}
|
||||
memory_writeU8(address + valueText.size(), 0x00);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (type == kDatatypeFloat)
|
||||
{
|
||||
auto value = memory_readFloat(address);
|
||||
m_listEntryTable->SetValue(fmt::format("{}", value), i, 3);
|
||||
}
|
||||
else if (type == kDatatypeDouble)
|
||||
{
|
||||
auto value = memory_readDouble(address);
|
||||
m_listEntryTable->SetValue(fmt::format("{}", value), i, 3);
|
||||
}
|
||||
else if (type == kDatatypeInt8)
|
||||
{
|
||||
auto value = (sint8)memory_readU8(address);
|
||||
m_listEntryTable->SetValue(fmt::format("{}", (int)value), i, 3);
|
||||
}
|
||||
else if (type == kDatatypeInt16)
|
||||
{
|
||||
auto value = (sint16)memory_readU16(address);
|
||||
m_listEntryTable->SetValue(fmt::format("{}", value), i, 3);
|
||||
}
|
||||
else if (type == kDatatypeInt32)
|
||||
{
|
||||
auto value = (sint32)memory_readU32(address);
|
||||
m_listEntryTable->SetValue(fmt::format("{}", value), i, 3);
|
||||
}
|
||||
else if (type == kDatatypeInt64)
|
||||
{
|
||||
auto value = (sint64)memory_readU64(address);
|
||||
m_listEntryTable->SetValue(fmt::format("{}", value), i, 3);
|
||||
}
|
||||
else if (type == kDatatypeString)
|
||||
{
|
||||
// TODO Peter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MemorySearcherTool::SetSearchDataType()
|
||||
{
|
||||
const auto type = m_cbDataType->GetStringSelection();
|
||||
if (type == kDatatypeFloat)
|
||||
m_searchDataType = SearchDataType_Float;
|
||||
else if (type == kDatatypeDouble)
|
||||
m_searchDataType = SearchDataType_Double;
|
||||
else if (type == kDatatypeInt8)
|
||||
m_searchDataType = SearchDataType_Int8;
|
||||
else if (type == kDatatypeInt16)
|
||||
m_searchDataType = SearchDataType_Int16;
|
||||
else if (type == kDatatypeInt32)
|
||||
m_searchDataType = SearchDataType_Int32;
|
||||
else if (type == kDatatypeInt64)
|
||||
m_searchDataType = SearchDataType_Int64;
|
||||
else if (type == kDatatypeString)
|
||||
m_searchDataType = SearchDataType_String;
|
||||
else
|
||||
m_searchDataType = SearchDataType_None;
|
||||
}
|
||||
|
||||
std::string MemorySearcherTool::GetSearchTypeName() const
|
||||
{
|
||||
switch (m_searchDataType)
|
||||
{
|
||||
case SearchDataType_String:
|
||||
return from_wxString(kDatatypeString);
|
||||
case SearchDataType_Float:
|
||||
return from_wxString(kDatatypeFloat);
|
||||
case SearchDataType_Double:
|
||||
return from_wxString(kDatatypeDouble);
|
||||
case SearchDataType_Int8:
|
||||
return from_wxString(kDatatypeInt8);
|
||||
case SearchDataType_Int16:
|
||||
return from_wxString(kDatatypeInt16);
|
||||
case SearchDataType_Int32:
|
||||
return from_wxString(kDatatypeInt32);
|
||||
case SearchDataType_Int64:
|
||||
return from_wxString(kDatatypeInt64);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template <>
|
||||
bool MemorySearcherTool::ConvertStringToType<signed char>(const char* inValue, sint8& outValue) const
|
||||
{
|
||||
sint16 tmp;
|
||||
std::istringstream iss(inValue);
|
||||
iss >> std::noskipws >> tmp;
|
||||
if (iss && iss.eof())
|
||||
{
|
||||
if (SCHAR_MIN <= tmp && tmp <= SCHAR_MAX)
|
||||
{
|
||||
outValue = tmp;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MemorySearcherTool::OnPopupClick(wxCommandEvent& event)
|
||||
{
|
||||
if (event.GetId() == LIST_ENTRY_REMOVE)
|
||||
{
|
||||
const int row = m_listEntryTable->GetSelectedRow();
|
||||
if (row == -1)
|
||||
return;
|
||||
|
||||
m_listEntryTable->DeleteItem(row);
|
||||
}
|
||||
}
|
||||
|
||||
void MemorySearcherTool::OnItemEdited(wxDataViewEvent& event)
|
||||
{
|
||||
auto column = event.GetColumn();
|
||||
// Edit description
|
||||
if (column == 0) { }
|
||||
// Edit value
|
||||
else if (column == 3)
|
||||
{
|
||||
auto row = m_listEntryTable->GetSelectedRow();
|
||||
if (row == -1)
|
||||
return;
|
||||
|
||||
auto addressText = std::string(m_listEntryTable->GetTextValue(row, 1).mbc_str());
|
||||
uint32 address = stoul(addressText, nullptr, 16);
|
||||
|
||||
auto type = m_listEntryTable->GetTextValue(row, 2);
|
||||
if (type == kDatatypeFloat)
|
||||
{
|
||||
auto value = (float)event.GetValue().GetDouble();
|
||||
memory_writeFloat(address, value);
|
||||
}
|
||||
else if (type == kDatatypeDouble)
|
||||
{
|
||||
auto value = event.GetValue().GetDouble();
|
||||
memory_writeDouble(address, value);
|
||||
}
|
||||
else if (type == kDatatypeInt8)
|
||||
{
|
||||
auto value = event.GetValue().GetInteger();
|
||||
memory_writeU8(address, value);
|
||||
}
|
||||
else if (type == kDatatypeInt16)
|
||||
{
|
||||
auto value = event.GetValue().GetInteger();
|
||||
memory_writeU16(address, value);
|
||||
}
|
||||
else if (type == kDatatypeInt32)
|
||||
{
|
||||
auto value = event.GetValue().GetInteger();
|
||||
memory_writeU32(address, value);
|
||||
}
|
||||
else if (type == kDatatypeInt64)
|
||||
{
|
||||
auto valueText = std::string(event.GetValue().GetString().mbc_str());
|
||||
auto value = stoull(valueText);
|
||||
memory_writeU64(address, value);
|
||||
}
|
||||
else if (type == kDatatypeString)
|
||||
{
|
||||
auto valueText = std::string(event.GetValue().GetString().mbc_str());
|
||||
for (int i = 0; i < valueText.size(); ++i)
|
||||
{
|
||||
memory_writeU8(address + i, valueText[i]);
|
||||
}
|
||||
memory_writeU8(address + valueText.size(), 0x00);
|
||||
}
|
||||
}
|
||||
}
|
||||
262
src/gui/MemorySearcherTool.h
Normal file
262
src/gui/MemorySearcherTool.h
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
|
||||
#include "Cafe/HW/MMU/MMU.h"
|
||||
#include "util/helpers/helpers.h"
|
||||
#include "gui/helpers/wxCustomEvents.h"
|
||||
|
||||
enum SearchDataType
|
||||
{
|
||||
SearchDataType_None,
|
||||
SearchDataType_String,
|
||||
SearchDataType_Float,
|
||||
SearchDataType_Double,
|
||||
SearchDataType_Int8,
|
||||
SearchDataType_Int16,
|
||||
SearchDataType_Int32,
|
||||
SearchDataType_Int64,
|
||||
};
|
||||
|
||||
struct TableEntry_t
|
||||
{
|
||||
uint32 address;
|
||||
const char description[32];
|
||||
SearchDataType type;
|
||||
bool freeze;
|
||||
uint16 dataLen;
|
||||
uint8* data;
|
||||
};
|
||||
|
||||
class MemorySearcherTool : public wxFrame
|
||||
{
|
||||
public:
|
||||
MemorySearcherTool(wxFrame* parent);
|
||||
virtual ~MemorySearcherTool();
|
||||
|
||||
void OnTimerTick(wxTimerEvent& event);
|
||||
void OnClose(wxCloseEvent&);
|
||||
void OnSearch(wxCommandEvent&);
|
||||
void OnSearchFinished(wxCommandEvent& event);
|
||||
void OnUpdateGauge(wxSetGaugeValue& event);
|
||||
void OnFilter(wxCommandEvent& event);
|
||||
void OnResultListClick(wxMouseEvent& event);
|
||||
void OnEntryListRightClick(wxDataViewEvent& event);
|
||||
//void OnEntryListRightClick2(wxContextMenuEvent& event);
|
||||
void OnPopupClick(wxCommandEvent& event);
|
||||
|
||||
void OnItemEdited(wxDataViewEvent& event);
|
||||
|
||||
private:
|
||||
void Reset();
|
||||
bool VerifySearchValue() const;
|
||||
void FillResultList();
|
||||
void RefreshResultList();
|
||||
void RefreshStashList();
|
||||
void SetSearchDataType();
|
||||
std::string GetSearchTypeName() const;
|
||||
void CreateRightClickPopupMenu();
|
||||
|
||||
void Load();
|
||||
void Save();
|
||||
|
||||
static bool IsAddressValid(uint32 addr);
|
||||
|
||||
template <typename T>
|
||||
bool ConvertStringToType(const char* inValue, T& outValue) const
|
||||
{
|
||||
std::istringstream iss(inValue);
|
||||
iss >> std::noskipws >> outValue;
|
||||
return iss && iss.eof();
|
||||
}
|
||||
|
||||
template <>
|
||||
bool ConvertStringToType(const char* inValue, sint8& outValue) const;
|
||||
|
||||
using ListType_t = std::vector<MEMPTR<void>>;
|
||||
ListType_t SearchValues(SearchDataType type, void* ptr, uint32 size)
|
||||
{
|
||||
/*if (type == SearchDataType_String)
|
||||
return SearchValues((const char* )ptr, size);
|
||||
else */if (type == SearchDataType_Float)
|
||||
return SearchValues((float*)ptr, size);
|
||||
else if (type == SearchDataType_Double)
|
||||
return SearchValues((double*)ptr, size);
|
||||
else if (type == SearchDataType_Int8)
|
||||
return SearchValues((sint8*)ptr, size);
|
||||
else if (type == SearchDataType_Int16)
|
||||
return SearchValues((sint16*)ptr, size);
|
||||
else if (type == SearchDataType_Int32)
|
||||
return SearchValues((sint32*)ptr, size);
|
||||
else if (type == SearchDataType_Int64)
|
||||
return SearchValues((sint64*)ptr, size);
|
||||
return {};
|
||||
}
|
||||
|
||||
ListType_t FilterValues(SearchDataType type)
|
||||
{
|
||||
/*if (type == SearchDataType_String)
|
||||
return FilterValues<const char*>();
|
||||
else */if (type == SearchDataType_Float)
|
||||
return FilterValues<float>();
|
||||
else if (type == SearchDataType_Double)
|
||||
return FilterValues<double>();
|
||||
else if (type == SearchDataType_Int8)
|
||||
return FilterValues<sint8>();
|
||||
else if (type == SearchDataType_Int16)
|
||||
return FilterValues<sint16>();
|
||||
else if (type == SearchDataType_Int32)
|
||||
return FilterValues<sint32>();
|
||||
else if (type == SearchDataType_Int64)
|
||||
return FilterValues<sint64>();
|
||||
return {};
|
||||
}
|
||||
|
||||
constexpr static int kGaugeStep = 0x10000;
|
||||
|
||||
template <typename T>
|
||||
ListType_t SearchValues(T* ptr, uint32 size)
|
||||
{
|
||||
const auto value = m_textValue->GetValue();
|
||||
const auto* string_value = value.c_str().AsChar();
|
||||
const auto search_value = ConvertString<T>(string_value);
|
||||
|
||||
const auto* end = (T*)((uint8*)ptr + size - sizeof(T));
|
||||
|
||||
uint32 counter = 0;
|
||||
std::vector<MEMPTR<void>> result;
|
||||
for (; ptr < end; ++ptr)
|
||||
{
|
||||
if (!m_running)
|
||||
return result;
|
||||
|
||||
const auto tmp = (betype<T>*)ptr;
|
||||
if (equals(search_value, tmp->value()))
|
||||
result.emplace_back(ptr);
|
||||
|
||||
counter += sizeof(T);
|
||||
if(counter >= kGaugeStep)
|
||||
{
|
||||
wxQueueEvent(this, new wxSetGaugeValue(1, m_gauge));
|
||||
counter -= kGaugeStep;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ListType_t FilterValues()
|
||||
{
|
||||
const auto value = m_textValue->GetValue();
|
||||
const auto* string_value = value.c_str().AsChar();
|
||||
const auto search_value = ConvertString<T>(string_value);
|
||||
|
||||
ListType_t newSearchBuffer;
|
||||
newSearchBuffer.reserve(m_searchBuffer.size());
|
||||
|
||||
for (const auto& it : m_searchBuffer)
|
||||
{
|
||||
if (!m_running)
|
||||
return newSearchBuffer;
|
||||
|
||||
const auto tmp = (betype<T>*)it.GetPtr();
|
||||
if (equals(search_value , tmp->value()))
|
||||
newSearchBuffer.emplace_back(it);
|
||||
|
||||
wxQueueEvent(this, new wxSetGaugeValue(1, m_gauge));
|
||||
}
|
||||
|
||||
return newSearchBuffer;
|
||||
}
|
||||
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
|
||||
SearchDataType m_searchDataType = SearchDataType_None;
|
||||
wxComboBox* m_cbDataType;
|
||||
wxTextCtrl* m_textValue;
|
||||
wxButton *m_buttonStart, *m_buttonFilter;
|
||||
wxListCtrl* m_listResults;
|
||||
wxDataViewListCtrl* m_listEntryTable;
|
||||
wxStaticText* m_textEntryTable;
|
||||
wxGauge* m_gauge;
|
||||
wxTimer* m_refresh_timer;
|
||||
|
||||
ListType_t m_searchBuffer;
|
||||
std::vector<TableEntry_t> m_tableEntries;
|
||||
|
||||
std::mutex m_mutex;
|
||||
std::thread m_worker;
|
||||
|
||||
std::atomic_bool m_running = true;
|
||||
std::atomic_bool m_search_running = false;
|
||||
std::vector<std::future<ListType_t>> m_search_jobs;
|
||||
|
||||
bool m_clear_state = false;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
//
|
||||
//template <typename T>
|
||||
//void MemorySearcherTool::FilterValues()
|
||||
//{
|
||||
// auto s1 = m_textValue->GetValue();
|
||||
// auto s2 = s1.c_str();
|
||||
// auto stringValue = s2.AsChar();
|
||||
//
|
||||
// T filterValue;
|
||||
// ConvertStringToType(stringValue, filterValue);
|
||||
//
|
||||
// std::vector<uint32> newSearchBuffer;
|
||||
// newSearchBuffer.reserve(m_searchBuffer.size());
|
||||
//
|
||||
// uint32 gaugeValue = 0;
|
||||
// uint32 count = m_searchBuffer.size();
|
||||
// uint32 counter = 0;
|
||||
//
|
||||
// for (auto it = m_searchBuffer.begin(); it != m_searchBuffer.end(); ++it)
|
||||
// {
|
||||
// if (m_running)
|
||||
// return;
|
||||
//
|
||||
// T value = memory_read<T>(*it);
|
||||
// if constexpr (std::is_same<T, float>::value)
|
||||
// {
|
||||
// //float value1 = filterValue * 0.95f;
|
||||
// float diff = filterValue - value;
|
||||
// diff = fabs(diff);
|
||||
// if(diff < value*0.05f)
|
||||
// {
|
||||
// newSearchBuffer.push_back(*it);
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if (value == filterValue)
|
||||
// {
|
||||
// newSearchBuffer.push_back(*it);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// ++counter;
|
||||
// uint32 newValue = counter * 100 / count;
|
||||
// if (newValue > gaugeValue + GAUGE_STEP_SIZE)
|
||||
// {
|
||||
// gaugeValue = newValue;
|
||||
// m_gaugeValue = newValue;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// m_mutex.lock();
|
||||
// m_searchBuffer = newSearchBuffer;
|
||||
// m_gaugeValue = 100;
|
||||
// m_mutex.unlock();
|
||||
// m_isSearchFinished = true;
|
||||
//}
|
||||
//
|
||||
|
||||
190
src/gui/PadViewFrame.cpp
Normal file
190
src/gui/PadViewFrame.cpp
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
#include "gui/wxgui.h"
|
||||
#include "gui/guiWrapper.h"
|
||||
#include "gui/PadViewFrame.h"
|
||||
|
||||
#include <wx/display.h>
|
||||
|
||||
#include "config/ActiveSettings.h"
|
||||
#include "Cafe/OS/libs/swkbd/swkbd.h"
|
||||
#include "gui/canvas/OpenGLCanvas.h"
|
||||
#include "gui/canvas/VulkanCanvas.h"
|
||||
#include "config/CemuConfig.h"
|
||||
#include "gui/MainWindow.h"
|
||||
#include "gui/helpers/wxHelpers.h"
|
||||
#include "input/InputManager.h"
|
||||
|
||||
#if BOOST_OS_LINUX
|
||||
#include "resource/linux/resources.h"
|
||||
#endif
|
||||
#include "wxHelper.h"
|
||||
|
||||
extern WindowInfo g_window_info;
|
||||
|
||||
PadViewFrame::PadViewFrame(wxFrame* parent)
|
||||
: wxFrame(nullptr, wxID_ANY, "GamePad View", wxDefaultPosition, wxSize(854, 480), wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxCLOSE_BOX | wxWANTS_CHARS)
|
||||
{
|
||||
gui_initHandleContextFromWxWidgetsWindow(g_window_info.window_pad, this);
|
||||
|
||||
SetIcon(wxICON(M_WND_ICON128));
|
||||
wxWindow::EnableTouchEvents(wxTOUCH_PAN_GESTURES);
|
||||
|
||||
SetMinClientSize({ 320, 180 });
|
||||
|
||||
SetPosition({ g_window_info.restored_pad_x, g_window_info.restored_pad_y });
|
||||
SetSize({ g_window_info.restored_pad_width, g_window_info.restored_pad_height });
|
||||
|
||||
if (g_window_info.pad_maximized)
|
||||
Maximize();
|
||||
|
||||
Bind(wxEVT_SIZE, &PadViewFrame::OnSizeEvent, this);
|
||||
Bind(wxEVT_MOVE, &PadViewFrame::OnMoveEvent, this);
|
||||
Bind(wxEVT_MOTION, &PadViewFrame::OnMouseMove, this);
|
||||
|
||||
Bind(wxEVT_SET_WINDOW_TITLE, &PadViewFrame::OnSetWindowTitle, this);
|
||||
|
||||
g_window_info.pad_open = true;
|
||||
}
|
||||
|
||||
PadViewFrame::~PadViewFrame()
|
||||
{
|
||||
g_window_info.pad_open = false;
|
||||
}
|
||||
|
||||
bool PadViewFrame::Initialize()
|
||||
{
|
||||
const wxSize client_size = GetClientSize();
|
||||
g_window_info.pad_width = client_size.GetWidth();
|
||||
g_window_info.pad_height = client_size.GetHeight();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PadViewFrame::InitializeRenderCanvas()
|
||||
{
|
||||
auto sizer = new wxBoxSizer(wxVERTICAL);
|
||||
{
|
||||
if (ActiveSettings::GetGraphicsAPI() == kVulkan)
|
||||
m_render_canvas = new VulkanCanvas(this, wxSize(854, 480), false);
|
||||
else
|
||||
m_render_canvas = GLCanvas_Create(this, wxSize(854, 480), false);
|
||||
sizer->Add(m_render_canvas, 1, wxEXPAND, 0, nullptr);
|
||||
}
|
||||
SetSizer(sizer);
|
||||
Layout();
|
||||
|
||||
m_render_canvas->Bind(wxEVT_KEY_UP, &PadViewFrame::OnKeyUp, this);
|
||||
m_render_canvas->Bind(wxEVT_CHAR, &PadViewFrame::OnChar, this);
|
||||
|
||||
m_render_canvas->Bind(wxEVT_MOTION, &PadViewFrame::OnMouseMove, this);
|
||||
m_render_canvas->Bind(wxEVT_LEFT_DOWN, &PadViewFrame::OnMouseLeft, this);
|
||||
m_render_canvas->Bind(wxEVT_LEFT_UP, &PadViewFrame::OnMouseLeft, this);
|
||||
m_render_canvas->Bind(wxEVT_RIGHT_DOWN, &PadViewFrame::OnMouseRight, this);
|
||||
m_render_canvas->Bind(wxEVT_RIGHT_UP, &PadViewFrame::OnMouseRight, this);
|
||||
|
||||
m_render_canvas->Bind(wxEVT_GESTURE_PAN, &PadViewFrame::OnGesturePan, this);
|
||||
|
||||
m_render_canvas->SetFocus();
|
||||
}
|
||||
|
||||
void PadViewFrame::OnSizeEvent(wxSizeEvent& event)
|
||||
{
|
||||
if (!IsMaximized() && !IsFullScreen())
|
||||
{
|
||||
g_window_info.restored_pad_width = GetSize().x;
|
||||
g_window_info.restored_pad_height = GetSize().y;
|
||||
}
|
||||
g_window_info.pad_maximized = IsMaximized() && !IsFullScreen();
|
||||
|
||||
const wxSize client_size = GetClientSize();
|
||||
g_window_info.pad_width = client_size.GetWidth();
|
||||
g_window_info.pad_height = client_size.GetHeight();
|
||||
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void PadViewFrame::OnMoveEvent(wxMoveEvent& event)
|
||||
{
|
||||
if (!IsMaximized() && !IsFullScreen())
|
||||
{
|
||||
g_window_info.restored_pad_x = GetPosition().x;
|
||||
g_window_info.restored_pad_y = GetPosition().y;
|
||||
}
|
||||
}
|
||||
|
||||
void PadViewFrame::OnKeyUp(wxKeyEvent& event)
|
||||
{
|
||||
event.Skip();
|
||||
|
||||
if (swkbd_hasKeyboardInputHook())
|
||||
return;
|
||||
|
||||
const auto code = event.GetKeyCode();
|
||||
if (code == WXK_ESCAPE)
|
||||
ShowFullScreen(false);
|
||||
else if (code == WXK_RETURN && event.AltDown())
|
||||
ShowFullScreen(!IsFullScreen());
|
||||
}
|
||||
|
||||
void PadViewFrame::OnGesturePan(wxPanGestureEvent& event)
|
||||
{
|
||||
auto& instance = InputManager::instance();
|
||||
|
||||
std::scoped_lock lock(instance.m_pad_touch.m_mutex);
|
||||
instance.m_pad_touch.position = { event.GetPosition().x, event.GetPosition().y };
|
||||
instance.m_pad_touch.left_down = event.IsGestureStart() || !event.IsGestureEnd();
|
||||
if (event.IsGestureStart() || !event.IsGestureEnd())
|
||||
instance.m_pad_touch.left_down_toggle = true;
|
||||
}
|
||||
|
||||
void PadViewFrame::OnChar(wxKeyEvent& event)
|
||||
{
|
||||
if (swkbd_hasKeyboardInputHook())
|
||||
swkbd_keyInput(event.GetUnicodeKey());
|
||||
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void PadViewFrame::OnMouseMove(wxMouseEvent& event)
|
||||
{
|
||||
auto& instance = InputManager::instance();
|
||||
|
||||
std::scoped_lock lock(instance.m_pad_touch.m_mutex);
|
||||
instance.m_pad_mouse.position = { event.GetPosition().x, event.GetPosition().y };
|
||||
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void PadViewFrame::OnMouseLeft(wxMouseEvent& event)
|
||||
{
|
||||
auto& instance = InputManager::instance();
|
||||
|
||||
std::scoped_lock lock(instance.m_pad_mouse.m_mutex);
|
||||
instance.m_pad_mouse.left_down = event.ButtonDown(wxMOUSE_BTN_LEFT);
|
||||
instance.m_pad_mouse.position = { event.GetPosition().x, event.GetPosition().y };
|
||||
if (event.ButtonDown(wxMOUSE_BTN_LEFT))
|
||||
instance.m_pad_mouse.left_down_toggle = true;
|
||||
|
||||
}
|
||||
|
||||
void PadViewFrame::OnMouseRight(wxMouseEvent& event)
|
||||
{
|
||||
auto& instance = InputManager::instance();
|
||||
|
||||
std::scoped_lock lock(instance.m_pad_mouse.m_mutex);
|
||||
instance.m_pad_mouse.right_down = event.ButtonDown(wxMOUSE_BTN_LEFT);
|
||||
instance.m_pad_mouse.position = { event.GetPosition().x, event.GetPosition().y };
|
||||
if (event.ButtonDown(wxMOUSE_BTN_RIGHT))
|
||||
instance.m_pad_mouse.right_down_toggle = true;
|
||||
}
|
||||
|
||||
void PadViewFrame::OnSetWindowTitle(wxCommandEvent& event)
|
||||
{
|
||||
this->SetTitle(event.GetString());
|
||||
}
|
||||
|
||||
void PadViewFrame::AsyncSetTitle(std::string_view windowTitle)
|
||||
{
|
||||
wxCommandEvent set_title_event(wxEVT_SET_WINDOW_TITLE);
|
||||
set_title_event.SetString(wxHelper::FromUtf8(windowTitle));
|
||||
QueueEvent(set_title_event.Clone());
|
||||
}
|
||||
34
src/gui/PadViewFrame.h
Normal file
34
src/gui/PadViewFrame.h
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
#define WM_CREATE_PAD (WM_USER+1)
|
||||
#define WM_DESTROY_PAD (WM_USER+2)
|
||||
|
||||
wxDECLARE_EVENT(EVT_PAD_CLOSE, wxCommandEvent);
|
||||
wxDECLARE_EVENT(EVT_SET_WINDOW_TITLE, wxCommandEvent);
|
||||
|
||||
class PadViewFrame : public wxFrame
|
||||
{
|
||||
public:
|
||||
PadViewFrame(wxFrame* parent);
|
||||
~PadViewFrame();
|
||||
|
||||
bool Initialize();
|
||||
void InitializeRenderCanvas();
|
||||
|
||||
void OnKeyUp(wxKeyEvent& event);
|
||||
void OnChar(wxKeyEvent& event);
|
||||
|
||||
void AsyncSetTitle(std::string_view windowTitle);
|
||||
|
||||
private:
|
||||
|
||||
void OnMouseMove(wxMouseEvent& event);
|
||||
void OnMouseLeft(wxMouseEvent& event);
|
||||
void OnMouseRight(wxMouseEvent& event);
|
||||
void OnSizeEvent(wxSizeEvent& event);
|
||||
void OnMoveEvent(wxMoveEvent& event);
|
||||
void OnGesturePan(wxPanGestureEvent& event);
|
||||
void OnSetWindowTitle(wxCommandEvent& event);
|
||||
|
||||
wxWindow* m_render_canvas = nullptr;
|
||||
};
|
||||
892
src/gui/TitleManager.cpp
Normal file
892
src/gui/TitleManager.cpp
Normal file
|
|
@ -0,0 +1,892 @@
|
|||
#include "gui/TitleManager.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <shellapi.h>
|
||||
#endif
|
||||
|
||||
#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/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"
|
||||
|
||||
#if BOOST_OS_LINUX
|
||||
#include "resource/linux/resources.h"
|
||||
#endif
|
||||
#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(PNG_REFRESH).ConvertToImage();
|
||||
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(PNG_HELP));
|
||||
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(_("Irrevocable delete the save entry "));
|
||||
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)
|
||||
: wxFrame(parent, wxID_ANY, _("Title manager"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE | wxTAB_TRAVERSAL)
|
||||
{
|
||||
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 {} titles, {} updates, {} DLCs and {} save entries"),
|
||||
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;
|
||||
std::string dirName = it.path().filename().string();
|
||||
uint32 persistentId = ConvertString<uint32>(dirName, 16);
|
||||
if (persistentId != 0)
|
||||
entry.persistent_ids.emplace_back(persistentId);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
#ifdef _WIN32
|
||||
ShellExecuteW(GetHWND(), L"open", target.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
|
||||
#else
|
||||
assert_dbg();
|
||||
#endif
|
||||
}
|
||||
|
||||
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)
|
||||
forceLog_printf("TitleManager::OnSaveDelete: couldn't delete save entry in saveinfo.xml: %s", saveinfo.generic_u8string().c_str());
|
||||
|
||||
// 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 a cemu_meta to the zip:\n{}"), zip_strerror(zip));
|
||||
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();
|
||||
}
|
||||
103
src/gui/TitleManager.h
Normal file
103
src/gui/TitleManager.h
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
#pragma once
|
||||
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
|
||||
#include <wx/frame.h>
|
||||
#include <wx/button.h>
|
||||
|
||||
#include "Cemu/Tools/DownloadManager/DownloadManager.h"
|
||||
|
||||
class wxCheckBox;
|
||||
class wxStaticText;
|
||||
class wxListEvent;
|
||||
class wxSetStatusBarTextEvent;
|
||||
class wxTitleManagerList;
|
||||
class wxDownloadManagerList;
|
||||
class wxTextCtrl;
|
||||
class wxStatusBar;
|
||||
class wxImageList;
|
||||
class wxBitmapButton;
|
||||
class wxPanel;
|
||||
class wxChoice;
|
||||
class wxNotebook;
|
||||
|
||||
enum class TitleManagerPage
|
||||
{
|
||||
TitleManager = 0,
|
||||
DownloadManager = 1
|
||||
};
|
||||
|
||||
enum class DLMGR_STATUS_CODE;
|
||||
|
||||
class TitleManager : public wxFrame
|
||||
{
|
||||
public:
|
||||
TitleManager(wxWindow* parent, TitleManagerPage default_page = TitleManagerPage::TitleManager);
|
||||
~TitleManager();
|
||||
|
||||
void SetFocusAndTab(TitleManagerPage page);
|
||||
|
||||
void SetDownloadStatusText(const wxString& text);
|
||||
|
||||
private:
|
||||
wxPanel* CreateTitleManagerPage();
|
||||
wxPanel* CreateDownloadManagerPage();
|
||||
|
||||
// title manager
|
||||
void OnTitleFound(wxCommandEvent& event);
|
||||
void OnTitleSearchComplete(wxCommandEvent& event);
|
||||
void OnSetStatusBarText(wxSetStatusBarTextEvent& event);
|
||||
void OnFilterChanged(wxCommandEvent& event);
|
||||
void OnRefreshButton(wxCommandEvent& event);
|
||||
void OnInstallTitle(wxCommandEvent& event);
|
||||
void OnTitleSelected(wxListEvent& event);
|
||||
void OnSaveOpenDirectory(wxCommandEvent& event);
|
||||
void OnSaveDelete(wxCommandEvent& event);
|
||||
void OnSaveTransfer(wxCommandEvent& event);
|
||||
void OnSaveAccountSelected(wxCommandEvent& event);
|
||||
void OnSaveExport(wxCommandEvent& event);
|
||||
void OnSaveImport(wxCommandEvent& event);
|
||||
|
||||
void HandleTitleListCallback(struct CafeTitleListCallbackEvent* evt);
|
||||
|
||||
wxNotebook* m_notebook;
|
||||
|
||||
uint32 m_callbackId;
|
||||
|
||||
// title manager
|
||||
wxTextCtrl* m_filter;
|
||||
wxTitleManagerList* m_title_list;
|
||||
wxStatusBar* m_status_bar;
|
||||
wxBitmapButton* m_refresh_button;
|
||||
wxPanel* m_save_panel;
|
||||
wxChoice* m_save_account_list;
|
||||
wxButton* m_save_import;
|
||||
|
||||
bool m_isScanning{ true }; // set when CafeTitleList is scanning
|
||||
|
||||
std::atomic_bool m_running = true;
|
||||
|
||||
// download manager
|
||||
void InitiateConnect();
|
||||
void OnConnect(wxCommandEvent& event);
|
||||
void OnSetStatusText(wxCommandEvent& event);
|
||||
void OnDownloadableTitleUpdate(wxCommandEvent& event);
|
||||
void OnDisconnect(wxCommandEvent& event);
|
||||
|
||||
void OnDlFilterCheckbox(wxCommandEvent& event);
|
||||
void OnDlCheckboxShowUpdates(wxCommandEvent& event);
|
||||
|
||||
void SetConnected(bool state);
|
||||
|
||||
static void Callback_ConnectStatusUpdate(std::string statusText, DLMGR_STATUS_CODE statusCode);
|
||||
static void Callback_AddDownloadableTitle(const struct DlMgrTitleReport& titleInfo);
|
||||
static void Callback_RemoveDownloadableTitle(uint64 titleId, uint16 version);
|
||||
|
||||
wxChoice* m_account;
|
||||
wxButton* m_connect;
|
||||
wxStaticText* m_status_text;
|
||||
wxCheckBox *m_show_titles, *m_show_updates, *m_show_installed;
|
||||
wxDownloadManagerList* m_download_list;
|
||||
bool m_connectRequested{false}; // connect was clicked before m_foundTitles was available
|
||||
};
|
||||
14
src/gui/canvas/IRenderCanvas.h
Normal file
14
src/gui/canvas/IRenderCanvas.h
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <wx/wxprec.h>
|
||||
|
||||
// base class for all render interfaces
|
||||
class IRenderCanvas
|
||||
{
|
||||
public:
|
||||
IRenderCanvas(bool is_main_window)
|
||||
: m_is_main_window(is_main_window) {}
|
||||
|
||||
protected:
|
||||
bool m_is_main_window;
|
||||
};
|
||||
96
src/gui/canvas/OpenGLCanvas.cpp
Normal file
96
src/gui/canvas/OpenGLCanvas.cpp
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
#include "gui/canvas/OpenGLCanvas.h"
|
||||
#include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h"
|
||||
|
||||
#include "config/CemuConfig.h"
|
||||
|
||||
#include "Common/GLInclude/GLInclude.h"
|
||||
#include <wx/glcanvas.h> // this includes GL/gl.h, avoid using this in a header because it would contaminate our own OpenGL definitions (GLInclude)
|
||||
|
||||
static const int g_gl_attribute_list[] =
|
||||
{
|
||||
WX_GL_RGBA,
|
||||
WX_GL_DOUBLEBUFFER,
|
||||
WX_GL_DEPTH_SIZE, 16,
|
||||
|
||||
WX_GL_MIN_RED, 8,
|
||||
WX_GL_MIN_GREEN, 8,
|
||||
WX_GL_MIN_BLUE, 8,
|
||||
WX_GL_MIN_ALPHA, 8,
|
||||
|
||||
WX_GL_STENCIL_SIZE, 8,
|
||||
|
||||
//WX_GL_MAJOR_VERSION, 4,
|
||||
//WX_GL_MINOR_VERSION, 1,
|
||||
//wx_GL_COMPAT_PROFILE,
|
||||
|
||||
0, // end of list
|
||||
};
|
||||
|
||||
wxGLContext* sGLContext = nullptr;
|
||||
class OpenGLCanvas* sGLTVView = nullptr;
|
||||
class OpenGLCanvas* sGLPadView = nullptr;
|
||||
|
||||
class OpenGLCanvas : public IRenderCanvas, public wxGLCanvas
|
||||
{
|
||||
public:
|
||||
OpenGLCanvas(wxWindow* parent, const wxSize& size, bool is_main_window)
|
||||
: IRenderCanvas(is_main_window), wxGLCanvas(parent, wxID_ANY, g_gl_attribute_list, wxDefaultPosition, size, wxFULL_REPAINT_ON_RESIZE | wxWANTS_CHARS)
|
||||
{
|
||||
cemuLog_logDebug(LogType::Force, "Creating OpenGL canvas");
|
||||
|
||||
if (m_is_main_window)
|
||||
{
|
||||
sGLTVView = this;
|
||||
sGLContext = new wxGLContext(this);
|
||||
|
||||
g_renderer = std::make_unique<OpenGLRenderer>();
|
||||
}
|
||||
else
|
||||
sGLPadView = this;
|
||||
|
||||
wxWindow::EnableTouchEvents(wxTOUCH_PAN_GESTURES);
|
||||
}
|
||||
|
||||
~OpenGLCanvas() override
|
||||
{
|
||||
// todo - if this is the main window, make sure the renderer has been shut down
|
||||
|
||||
if (m_is_main_window)
|
||||
sGLTVView = nullptr;
|
||||
else
|
||||
sGLPadView = nullptr;
|
||||
|
||||
if (sGLTVView == nullptr && sGLPadView == nullptr && sGLContext)
|
||||
delete sGLContext;
|
||||
}
|
||||
|
||||
private:
|
||||
//wxGLContext* m_context = nullptr;
|
||||
};
|
||||
|
||||
wxWindow* GLCanvas_Create(wxWindow* parent, const wxSize& size, bool is_main_window)
|
||||
{
|
||||
return new OpenGLCanvas(parent, size, is_main_window);
|
||||
}
|
||||
|
||||
bool GLCanvas_HasPadViewOpen()
|
||||
{
|
||||
return sGLPadView != nullptr;
|
||||
}
|
||||
|
||||
bool GLCanvas_MakeCurrent(bool padView)
|
||||
{
|
||||
OpenGLCanvas* canvas = padView ? sGLPadView : sGLTVView;
|
||||
if (!canvas)
|
||||
return false;
|
||||
sGLContext->SetCurrent(*canvas);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GLCanvas_SwapBuffers(bool swapTV, bool swapDRC)
|
||||
{
|
||||
if (swapTV && sGLTVView)
|
||||
sGLTVView->SwapBuffers();
|
||||
if (swapDRC && sGLPadView)
|
||||
sGLPadView->SwapBuffers();
|
||||
}
|
||||
8
src/gui/canvas/OpenGLCanvas.h
Normal file
8
src/gui/canvas/OpenGLCanvas.h
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#pragma once
|
||||
#include <wx/window.h>
|
||||
#include "gui/canvas/IRenderCanvas.h"
|
||||
|
||||
wxWindow* GLCanvas_Create(wxWindow* parent, const wxSize& size, bool is_main_window);
|
||||
bool GLCanvas_MakeCurrent(bool padView);
|
||||
void GLCanvas_SwapBuffers(bool swapTV, bool swapDRC);
|
||||
bool GLCanvas_HasPadViewOpen();
|
||||
64
src/gui/canvas/VulkanCanvas.cpp
Normal file
64
src/gui/canvas/VulkanCanvas.cpp
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#include "gui/canvas/VulkanCanvas.h"
|
||||
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h"
|
||||
#include "gui/guiWrapper.h"
|
||||
|
||||
#include <wx/msgdlg.h>
|
||||
|
||||
VulkanCanvas::VulkanCanvas(wxWindow* parent, const wxSize& size, bool is_main_window)
|
||||
: IRenderCanvas(is_main_window), wxWindow(parent, wxID_ANY, wxDefaultPosition, size, wxFULL_REPAINT_ON_RESIZE | wxWANTS_CHARS)
|
||||
{
|
||||
Bind(wxEVT_PAINT, &VulkanCanvas::OnPaint, this);
|
||||
Bind(wxEVT_SIZE, &VulkanCanvas::OnResize, this);
|
||||
|
||||
if(is_main_window)
|
||||
gui_initHandleContextFromWxWidgetsWindow(gui_getWindowInfo().canvas_main, this);
|
||||
else
|
||||
gui_initHandleContextFromWxWidgetsWindow(gui_getWindowInfo().canvas_pad, this);
|
||||
|
||||
cemu_assert(g_vulkan_available);
|
||||
|
||||
try
|
||||
{
|
||||
if (is_main_window)
|
||||
g_renderer = std::make_unique<VulkanRenderer>();
|
||||
|
||||
auto vulkan_renderer = VulkanRenderer::GetInstance();
|
||||
vulkan_renderer->Initialize({size.x, size.y}, is_main_window);
|
||||
}
|
||||
catch(const std::exception& ex)
|
||||
{
|
||||
const auto msg = fmt::format(_("Error when initializing Vulkan renderer:\n{}").ToStdString(), ex.what());
|
||||
forceLog_printf(const_cast<char*>(msg.c_str()));
|
||||
wxMessageDialog dialog(this, msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
|
||||
dialog.ShowModal();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
wxWindow::EnableTouchEvents(wxTOUCH_PAN_GESTURES);
|
||||
}
|
||||
|
||||
VulkanCanvas::~VulkanCanvas()
|
||||
{
|
||||
Unbind(wxEVT_PAINT, &VulkanCanvas::OnPaint, this);
|
||||
Unbind(wxEVT_SIZE, &VulkanCanvas::OnResize, this);
|
||||
}
|
||||
|
||||
void VulkanCanvas::OnPaint(wxPaintEvent& event)
|
||||
{
|
||||
}
|
||||
|
||||
void VulkanCanvas::OnResize(wxSizeEvent& event)
|
||||
{
|
||||
const wxSize size = GetSize();
|
||||
if (size.GetWidth() == 0 || size.GetHeight() == 0)
|
||||
return;
|
||||
|
||||
const wxRect refreshRect(size);
|
||||
RefreshRect(refreshRect, false);
|
||||
|
||||
if (g_renderer == nullptr)
|
||||
return;
|
||||
|
||||
auto vulkan_renderer = VulkanRenderer::GetInstance();
|
||||
vulkan_renderer->ResizeSwapchain({ size.x, size.y }, m_is_main_window);
|
||||
}
|
||||
22
src/gui/canvas/VulkanCanvas.h
Normal file
22
src/gui/canvas/VulkanCanvas.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include "gui/canvas/IRenderCanvas.h"
|
||||
|
||||
#include <wx/frame.h>
|
||||
|
||||
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h"
|
||||
#include <set>
|
||||
|
||||
|
||||
|
||||
class VulkanCanvas : public IRenderCanvas, public wxWindow
|
||||
{
|
||||
public:
|
||||
VulkanCanvas(wxWindow* parent, const wxSize& size, bool is_main_window);
|
||||
~VulkanCanvas();
|
||||
|
||||
private:
|
||||
|
||||
void OnPaint(wxPaintEvent& event);
|
||||
void OnResize(wxSizeEvent& event);
|
||||
};
|
||||
317
src/gui/components/TextList.cpp
Normal file
317
src/gui/components/TextList.cpp
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
#include "gui/wxgui.h"
|
||||
#include "TextList.h"
|
||||
#include <wx/setup.h>
|
||||
#include <wx/tooltip.h>
|
||||
|
||||
TextList::~TextList()
|
||||
{
|
||||
m_tooltip_timer->Stop();
|
||||
|
||||
this->Unbind(wxEVT_MOTION, &TextList::OnMouseMoveEvent, this);
|
||||
this->Unbind(wxEVT_KEY_DOWN, &TextList::OnKeyDownEvent, this);
|
||||
this->Unbind(wxEVT_KEY_UP, &TextList::OnKeyUpEvent, this);
|
||||
this->Unbind(wxEVT_PAINT, &TextList::OnPaintEvent, this);
|
||||
this->Unbind(wxEVT_LEFT_DOWN, &TextList::OnMouseDownEvent, this);
|
||||
this->Unbind(wxEVT_LEFT_UP, &TextList::OnMouseUpEvent, this);
|
||||
this->Unbind(wxEVT_LEFT_DCLICK, &TextList::OnMouseDClickEvent, this);
|
||||
this->Unbind(wxEVT_CONTEXT_MENU, &TextList::OnContextMenu, this);
|
||||
this->Unbind(wxEVT_ERASE_BACKGROUND, &TextList::OnEraseBackground, this);
|
||||
}
|
||||
|
||||
TextList::TextList(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
|
||||
: wxControl(parent, id, pos, size, style), wxScrollHelper(this)
|
||||
{
|
||||
m_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
wxWindowBase::SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
|
||||
wxClientDC dc(this);
|
||||
m_line_height = dc.GetCharHeight();
|
||||
m_char_width = dc.GetCharWidth();
|
||||
|
||||
m_yScrollPixelsPerLine = m_line_height;
|
||||
|
||||
this->ShowScrollbars(wxSHOW_SB_DEFAULT, wxSHOW_SB_DEFAULT);
|
||||
|
||||
m_tooltip = new wxToolTip(wxEmptyString);
|
||||
|
||||
this->Bind(wxEVT_MOTION, &TextList::OnMouseMoveEvent, this);
|
||||
this->Bind(wxEVT_KEY_DOWN, &TextList::OnKeyDownEvent, this);
|
||||
this->Bind(wxEVT_KEY_UP, &TextList::OnKeyUpEvent, this);
|
||||
this->Bind(wxEVT_PAINT, &TextList::OnPaintEvent, this);
|
||||
this->Bind(wxEVT_LEFT_DOWN, &TextList::OnMouseDownEvent, this);
|
||||
this->Bind(wxEVT_LEFT_UP, &TextList::OnMouseUpEvent, this);
|
||||
this->Bind(wxEVT_LEFT_DCLICK, &TextList::OnMouseDClickEvent, this);
|
||||
this->Bind(wxEVT_CONTEXT_MENU, &TextList::OnContextMenu, this);
|
||||
this->Bind(wxEVT_ERASE_BACKGROUND, &TextList::OnEraseBackground, this);
|
||||
|
||||
m_tooltip_window = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNO_BORDER);
|
||||
m_tooltip_window->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOBK));
|
||||
m_tooltip_window->Hide();
|
||||
|
||||
m_tooltip_timer = new wxTimer(this);
|
||||
this->Bind(wxEVT_TIMER, &TextList::OnTooltipTimer, this);
|
||||
}
|
||||
|
||||
void TextList::RefreshControl(const wxRect* update_region)
|
||||
{
|
||||
if(update_region)
|
||||
Refresh(true, update_region);
|
||||
else
|
||||
{
|
||||
wxRect region = GetClientRect();
|
||||
update_region = ®ion;
|
||||
Refresh(true, update_region);
|
||||
}
|
||||
}
|
||||
|
||||
void TextList::RefreshLine(uint32 line)
|
||||
{
|
||||
wxRect update_region = GetClientRect();
|
||||
update_region.y = (sint32)line * m_line_height;
|
||||
update_region.height = m_line_height;
|
||||
CalcScrolledPosition(0, update_region.y, nullptr, &update_region.y);
|
||||
// debug_printf("update: <%x, %x>\n", update_region.y, update_region.height);
|
||||
Refresh(true, &update_region);
|
||||
}
|
||||
|
||||
wxSize TextList::DoGetVirtualSize() const
|
||||
{
|
||||
return {wxDefaultCoord, (int)(m_element_count + 1) * m_line_height};
|
||||
}
|
||||
|
||||
void TextList::DoSetSize(int x, int y, int width, int height, int sizeFlags)
|
||||
{
|
||||
wxControl::DoSetSize(x, y, width, height, sizeFlags);
|
||||
|
||||
m_elements_visible = (height + m_line_height - 1) / m_line_height;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void TextList::SetScrollPos(int orient, int pos, bool refresh)
|
||||
{
|
||||
wxControl::SetScrollPos(orient, pos, refresh);
|
||||
}
|
||||
|
||||
void TextList::DrawLineBackground(wxDC& dc, uint32 line, const wxColour& colour, uint32 lines) const
|
||||
{
|
||||
wxRect rect;
|
||||
rect.x = GetPosition().x;
|
||||
rect.y = line * m_line_height;
|
||||
rect.width = GetSize().x;
|
||||
rect.height = m_line_height * lines;
|
||||
|
||||
dc.SetBrush(colour);
|
||||
dc.DrawRectangle(rect);
|
||||
}
|
||||
|
||||
void TextList::DrawLineBackground(wxDC& dc, const wxPoint& position, const wxColour& colour, uint32 lines) const
|
||||
{
|
||||
wxRect rect;
|
||||
rect.x = position.x;
|
||||
rect.y = position.y;
|
||||
rect.width = this->GetSize().x;
|
||||
rect.height = m_line_height * lines;
|
||||
|
||||
dc.SetBrush(colour);
|
||||
dc.DrawRectangle(rect);
|
||||
}
|
||||
|
||||
bool TextList::SetElementCount(size_t element_count)
|
||||
{
|
||||
if (m_element_count == element_count)
|
||||
return false;
|
||||
|
||||
if (element_count > 0x7FFFFFFF)
|
||||
element_count = 0x7FFFFFFF;
|
||||
|
||||
m_element_count = element_count;
|
||||
this->AdjustScrollbars();
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32 TextList::GetElementCount() const
|
||||
{
|
||||
return m_element_count;
|
||||
}
|
||||
|
||||
bool TextList::IsKeyDown(sint32 keycode)
|
||||
{
|
||||
return m_key_states[keycode];
|
||||
}
|
||||
|
||||
void TextList::WriteText(wxDC& dc, const wxString& text, wxPoint& position) const
|
||||
{
|
||||
dc.ResetBoundingBox();
|
||||
dc.DrawText(text, position);
|
||||
position.x += dc.MaxX() - dc.MinX();
|
||||
}
|
||||
|
||||
void TextList::WriteText(wxDC& dc, const wxString& text, wxPoint& position, const wxColour& color) const
|
||||
{
|
||||
dc.SetTextForeground(color);
|
||||
WriteText(dc, text, position);
|
||||
}
|
||||
|
||||
void TextList::NextLine(wxPoint& position, const wxPoint* start_position) const
|
||||
{
|
||||
position.y += m_line_height;
|
||||
|
||||
if (start_position)
|
||||
position.x = start_position->x;
|
||||
}
|
||||
|
||||
void TextList::OnMouseDown() {}
|
||||
void TextList::OnMouseUp() {}
|
||||
|
||||
bool TextList::OnShowTooltip(const wxPoint& position, uint32 line)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void TextList::OnMouseMoveEvent(wxMouseEvent& event)
|
||||
{
|
||||
m_tooltip_timer->Stop();
|
||||
m_tooltip_timer->StartOnce(250);
|
||||
|
||||
wxPoint position = event.GetPosition();
|
||||
CalcUnscrolledPosition(position.x, position.y, &position.x, &position.y);
|
||||
|
||||
m_mouse_position = position;
|
||||
|
||||
if(m_mouse_down)
|
||||
m_selection.SetBottomRight(position);
|
||||
|
||||
const sint32 line = position.y / m_line_height;
|
||||
OnMouseMove(position, line);
|
||||
}
|
||||
|
||||
void TextList::OnKeyDownEvent(wxKeyEvent& event)
|
||||
{
|
||||
const auto key_code = event.GetKeyCode();
|
||||
const auto it = m_key_states.find(key_code);
|
||||
if(it == m_key_states.end() || !it->second)
|
||||
{
|
||||
m_key_states[key_code] = true;
|
||||
OnKeyPressed(key_code, event.GetPosition());
|
||||
}
|
||||
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void TextList::OnKeyUpEvent(wxKeyEvent& event)
|
||||
{
|
||||
m_key_states[event.GetKeyCode()] = false;
|
||||
}
|
||||
|
||||
void TextList::OnMouseDownEvent(wxMouseEvent& event)
|
||||
{
|
||||
m_mouse_down = true;
|
||||
|
||||
wxPoint position = event.GetPosition();
|
||||
CalcUnscrolledPosition(position.x, position.y, &position.x, &position.y);
|
||||
m_selection.SetPosition(position);
|
||||
m_selection.SetBottomRight(position);
|
||||
|
||||
OnMouseDown();
|
||||
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void TextList::OnMouseUpEvent(wxMouseEvent& event)
|
||||
{
|
||||
m_mouse_down = false;
|
||||
OnMouseUp();
|
||||
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void TextList::OnMouseDClickEvent(wxMouseEvent& event)
|
||||
{
|
||||
wxPoint position = event.GetPosition();
|
||||
CalcUnscrolledPosition(position.x, position.y, &position.x, &position.y);
|
||||
m_selection.SetPosition(position);
|
||||
m_selection.SetBottomRight(position);
|
||||
|
||||
const uint32 line = position.y / m_line_height;
|
||||
OnMouseDClick(position, line);
|
||||
}
|
||||
|
||||
void TextList::OnContextMenu(wxContextMenuEvent& event)
|
||||
{
|
||||
wxPoint position = event.GetPosition();
|
||||
if (position == wxDefaultPosition)
|
||||
return;
|
||||
|
||||
wxPoint clientPosition = ScreenToClient(position);
|
||||
|
||||
CalcUnscrolledPosition(clientPosition.x, clientPosition.y, &clientPosition.x, &clientPosition.y);
|
||||
m_selection.SetPosition(clientPosition);
|
||||
m_selection.SetBottomRight(clientPosition);
|
||||
|
||||
const uint32 line = clientPosition.y / m_line_height;
|
||||
OnContextMenu(clientPosition, line);
|
||||
}
|
||||
|
||||
|
||||
void TextList::OnTooltipTimer(wxTimerEvent& event)
|
||||
{
|
||||
m_tooltip_window->Hide();
|
||||
|
||||
const auto cursor_position = wxGetMousePosition();
|
||||
|
||||
const auto position = GetScreenPosition();
|
||||
if (cursor_position.x < position.x || cursor_position.y < position.y)
|
||||
return;
|
||||
|
||||
const auto size = GetSize();
|
||||
if (position.x + size.x < cursor_position.x || position.y + size.y < cursor_position.y)
|
||||
return;
|
||||
|
||||
const sint32 line = position.y / m_line_height;
|
||||
if(OnShowTooltip(m_mouse_position, line))
|
||||
{
|
||||
m_tooltip_window->SetPosition(wxPoint(m_mouse_position.x + 15, m_mouse_position.y + 15));
|
||||
m_tooltip_window->SendSizeEvent();
|
||||
m_tooltip_window->Show();
|
||||
}
|
||||
}
|
||||
|
||||
void TextList::OnPaintEvent(wxPaintEvent& event)
|
||||
{
|
||||
wxBufferedPaintDC dc(m_targetWindow, wxBUFFER_VIRTUAL_AREA);
|
||||
dc.SetFont(m_font);
|
||||
|
||||
// get window position
|
||||
auto position = GetPosition();
|
||||
|
||||
// get current real position
|
||||
wxRect rect_update = GetUpdateRegion().GetBox();
|
||||
const auto count = (uint32)std::ceil((float)rect_update.GetHeight() / m_line_height);
|
||||
|
||||
position.y = (rect_update.y / m_line_height) * m_line_height;
|
||||
|
||||
// paint background
|
||||
const wxColour window_colour = COLOR_WHITE;
|
||||
dc.SetBrush(window_colour);
|
||||
dc.SetPen(window_colour);
|
||||
dc.DrawRectangle(rect_update);
|
||||
|
||||
//// paint selection
|
||||
//if (!m_selected_text.eof())
|
||||
//{
|
||||
// dc.SetBrush(*wxBLUE_BRUSH);
|
||||
// dc.SetPen(*wxBLUE_PEN);
|
||||
// dc.DrawRectangle(m_selection);
|
||||
//}
|
||||
|
||||
sint32 start;
|
||||
CalcUnscrolledPosition(rect_update.x, rect_update.y, nullptr, &start);
|
||||
|
||||
start /= m_line_height;
|
||||
m_scrolled_to_end = (start + count) >= m_element_count;
|
||||
|
||||
OnDraw(dc, start, count, position);
|
||||
|
||||
|
||||
this->Update();
|
||||
}
|
||||
79
src/gui/components/TextList.h
Normal file
79
src/gui/components/TextList.h
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
#pragma once
|
||||
|
||||
#include <wx/wx.h>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <sstream>
|
||||
|
||||
#define COLOR_BLACK 0xFF000000
|
||||
#define COLOR_GREY 0xFFA0A0A0
|
||||
#define COLOR_LIGHT_GREY 0xFFE0E0E0
|
||||
#define COLOR_WHITE 0xFFFFFFFF
|
||||
#define COLOR_RED 0xFF0000FF
|
||||
|
||||
|
||||
class TextList : public wxControl, public wxScrollHelper
|
||||
{
|
||||
public:
|
||||
virtual ~TextList();
|
||||
TextList(wxWindow* parent, wxWindowID id,
|
||||
const wxPoint& pos = wxDefaultPosition,
|
||||
const wxSize& size = wxDefaultSize, long style = 0);
|
||||
|
||||
void RefreshControl(const wxRect* update_region = nullptr);
|
||||
void RefreshLine(uint32 line);
|
||||
|
||||
wxSize DoGetVirtualSize() const override;
|
||||
void DoSetSize(int x, int y, int width, int height, int sizeFlags) override;
|
||||
void SetScrollPos(int orient, int pos, bool refresh) override;
|
||||
void DrawLineBackground(wxDC& dc, uint32 line, const wxColour& colour, uint32 lines = 1) const;
|
||||
void DrawLineBackground(wxDC& dc, const wxPoint& position, const wxColour& colour, uint32 lines = 1) const;
|
||||
|
||||
bool SetElementCount(size_t element_count);
|
||||
uint32 GetElementCount() const;
|
||||
bool IsKeyDown(sint32 keycode);
|
||||
|
||||
protected:
|
||||
void WriteText(wxDC& dc, const wxString& text, wxPoint& position) const;
|
||||
void WriteText(wxDC& dc, const wxString& text, wxPoint& position, const wxColour& color) const;
|
||||
void NextLine(wxPoint& position, const wxPoint* start_position = nullptr) const;
|
||||
|
||||
virtual void OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position) {};
|
||||
virtual void OnMouseMove(const wxPoint& position, uint32 line) {}
|
||||
virtual void OnKeyPressed(sint32 key_code, const wxPoint& position) {}
|
||||
virtual void OnMouseDown();
|
||||
virtual void OnMouseUp();
|
||||
virtual void OnMouseDClick(const wxPoint& position, uint32 line) {}
|
||||
virtual void OnContextMenu(const wxPoint& position, uint32 line) {}
|
||||
virtual bool OnShowTooltip(const wxPoint& position, uint32 line);
|
||||
void OnEraseBackground(wxEraseEvent&) {}
|
||||
|
||||
sint32 m_line_height;
|
||||
sint32 m_char_width;
|
||||
size_t m_elements_visible = 0;
|
||||
size_t m_element_count = 0;
|
||||
bool m_scrolled_to_end = true;
|
||||
|
||||
std::wstringstream m_selected_text;
|
||||
bool m_mouse_down = false;
|
||||
wxRect m_selection;
|
||||
wxPanel* m_tooltip_window;
|
||||
|
||||
private:
|
||||
void OnPaintEvent(wxPaintEvent& event);
|
||||
void OnMouseMoveEvent(wxMouseEvent& event);
|
||||
void OnKeyDownEvent(wxKeyEvent& event);
|
||||
void OnKeyUpEvent(wxKeyEvent& event);
|
||||
void OnMouseDownEvent(wxMouseEvent& event);
|
||||
void OnMouseUpEvent(wxMouseEvent& event);
|
||||
void OnMouseDClickEvent(wxMouseEvent& event);
|
||||
void OnContextMenu(wxContextMenuEvent& event);
|
||||
void OnTooltipTimer(wxTimerEvent& event);
|
||||
|
||||
std::unordered_map<sint32, bool> m_key_states;
|
||||
|
||||
wxFont m_font;
|
||||
|
||||
wxTimer* m_tooltip_timer;
|
||||
wxPoint m_mouse_position;
|
||||
};
|
||||
779
src/gui/components/wxDownloadManagerList.cpp
Normal file
779
src/gui/components/wxDownloadManagerList.cpp
Normal file
|
|
@ -0,0 +1,779 @@
|
|||
#include "gui/components/wxDownloadManagerList.h"
|
||||
|
||||
#include "gui/helpers/wxHelpers.h"
|
||||
#include "util/helpers/SystemException.h"
|
||||
#include "Cafe/TitleList/GameInfo.h"
|
||||
#include "gui/components/wxGameList.h"
|
||||
#include "gui/helpers/wxCustomEvents.h"
|
||||
|
||||
#include <wx/imaglist.h>
|
||||
#include <wx/wupdlock.h>
|
||||
#include <wx/menu.h>
|
||||
#include <wx/msgdlg.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/timer.h>
|
||||
#include <wx/panel.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "config/ActiveSettings.h"
|
||||
#include "gui/ChecksumTool.h"
|
||||
#include "Cemu/Tools/DownloadManager/DownloadManager.h"
|
||||
#include "Cafe/TitleList/TitleId.h"
|
||||
#include "gui/MainWindow.h"
|
||||
|
||||
wxDEFINE_EVENT(wxEVT_REMOVE_ENTRY, wxCommandEvent);
|
||||
|
||||
|
||||
wxDownloadManagerList::wxDownloadManagerList(wxWindow* parent, wxWindowID id)
|
||||
: wxListCtrl(parent, id, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_VIRTUAL)
|
||||
{
|
||||
AddColumns();
|
||||
|
||||
// tooltip TODO: extract class mb wxPanelTooltip
|
||||
m_tooltip_window = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNO_BORDER);
|
||||
auto* tooltip_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_tooltip_text = new wxStaticText(m_tooltip_window, wxID_ANY, wxEmptyString);
|
||||
tooltip_sizer->Add(m_tooltip_text , 0, wxALL, 5);
|
||||
m_tooltip_window->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOBK));
|
||||
m_tooltip_window->SetSizerAndFit(tooltip_sizer);
|
||||
m_tooltip_window->Hide();
|
||||
m_tooltip_timer = new wxTimer(this);
|
||||
|
||||
Bind(wxEVT_LIST_COL_CLICK, &wxDownloadManagerList::OnColumnClick, this);
|
||||
Bind(wxEVT_CONTEXT_MENU, &wxDownloadManagerList::OnContextMenu, this);
|
||||
Bind(wxEVT_LIST_ITEM_SELECTED, &wxDownloadManagerList::OnItemSelected, this);
|
||||
Bind(wxEVT_TIMER, &wxDownloadManagerList::OnTimer, this);
|
||||
Bind(wxEVT_REMOVE_ITEM, &wxDownloadManagerList::OnRemoveItem, this);
|
||||
Bind(wxEVT_REMOVE_ENTRY, &wxDownloadManagerList::OnRemoveEntry, this);
|
||||
Bind(wxEVT_CLOSE_WINDOW, &wxDownloadManagerList::OnClose, this);
|
||||
}
|
||||
|
||||
boost::optional<const wxDownloadManagerList::TitleEntry&> wxDownloadManagerList::GetSelectedTitleEntry() const
|
||||
{
|
||||
const auto selection = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
|
||||
if (selection != wxNOT_FOUND)
|
||||
{
|
||||
const auto tmp = GetTitleEntry(selection);
|
||||
if (tmp.has_value())
|
||||
return tmp.value();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
boost::optional<wxDownloadManagerList::TitleEntry&> wxDownloadManagerList::GetSelectedTitleEntry()
|
||||
{
|
||||
const auto selection = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
|
||||
if (selection != wxNOT_FOUND)
|
||||
{
|
||||
const auto tmp = GetTitleEntry(selection);
|
||||
if (tmp.has_value())
|
||||
return tmp.value();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
boost::optional<wxDownloadManagerList::TitleEntry&> wxDownloadManagerList::GetTitleEntry(uint64 titleId, uint16 titleVersion)
|
||||
{
|
||||
for(const auto& v : m_data)
|
||||
{
|
||||
if (v->entry.titleId == titleId && v->entry.version == titleVersion)
|
||||
return v->entry;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::AddColumns()
|
||||
{
|
||||
wxListItem col0;
|
||||
col0.SetId(ColumnTitleId);
|
||||
col0.SetText(_("Title id"));
|
||||
col0.SetWidth(120);
|
||||
InsertColumn(ColumnTitleId, col0);
|
||||
|
||||
wxListItem col1;
|
||||
col1.SetId(ColumnName);
|
||||
col1.SetText(_("Name"));
|
||||
col1.SetWidth(260);
|
||||
InsertColumn(ColumnName, col1);
|
||||
|
||||
wxListItem col2;
|
||||
col2.SetId(ColumnVersion);
|
||||
col2.SetText(_("Version"));
|
||||
col2.SetWidth(55);
|
||||
InsertColumn(ColumnVersion, col2);
|
||||
|
||||
wxListItem col3;
|
||||
col3.SetId(ColumnType);
|
||||
col3.SetText(_("Type"));
|
||||
col3.SetWidth(60);
|
||||
InsertColumn(ColumnType, col3);
|
||||
|
||||
wxListItem col4;
|
||||
col4.SetId(ColumnProgress);
|
||||
col4.SetText(_("Progress"));
|
||||
col4.SetWidth(wxLIST_AUTOSIZE_USEHEADER);
|
||||
InsertColumn(ColumnProgress, col4);
|
||||
|
||||
wxListItem col5;
|
||||
col5.SetId(ColumnStatus);
|
||||
col5.SetText(_("Status"));
|
||||
col5.SetWidth(240);
|
||||
InsertColumn(ColumnStatus, col5);
|
||||
}
|
||||
|
||||
wxString wxDownloadManagerList::OnGetItemText(long item, long column) const
|
||||
{
|
||||
if (item >= GetItemCount())
|
||||
return wxEmptyString;
|
||||
|
||||
const auto entry = GetTitleEntry(item);
|
||||
if (entry.has_value())
|
||||
return GetTitleEntryText(entry.value(), (ItemColumn)column);
|
||||
|
||||
return wxEmptyString;
|
||||
}
|
||||
|
||||
wxItemAttr* wxDownloadManagerList::OnGetItemAttr(long item) const
|
||||
{
|
||||
const auto entry = GetTitleEntry(item);
|
||||
if (entry.has_value())
|
||||
{
|
||||
auto& entryData = entry.value();
|
||||
if (entryData.status == TitleDownloadStatus::Downloading ||
|
||||
entryData.status == TitleDownloadStatus::Verifying ||
|
||||
entryData.status == TitleDownloadStatus::Installing)
|
||||
{
|
||||
const wxColour kActiveColor{ 0xFFE0E0 };
|
||||
static wxListItemAttr s_error_attr(GetTextColour(), kActiveColor, GetFont());
|
||||
return &s_error_attr;
|
||||
}
|
||||
else if (entryData.status == TitleDownloadStatus::Installed && entryData.isPackage)
|
||||
{
|
||||
const wxColour kActiveColor{ 0xE0FFE0 };
|
||||
static wxListItemAttr s_error_attr(GetTextColour(), kActiveColor, GetFont());
|
||||
return &s_error_attr;
|
||||
}
|
||||
else if (entryData.status == TitleDownloadStatus::Error)
|
||||
{
|
||||
const wxColour kActiveColor{ 0xCCCCF2 };
|
||||
static wxListItemAttr s_error_attr(GetTextColour(), kActiveColor, GetFont());
|
||||
return &s_error_attr;
|
||||
}
|
||||
}
|
||||
|
||||
const wxColour kSecondColor{ 0xFDF9F2 };
|
||||
static wxListItemAttr s_coloured_attr(GetTextColour(), kSecondColor, GetFont());
|
||||
return item % 2 == 0 ? nullptr : &s_coloured_attr;
|
||||
}
|
||||
|
||||
boost::optional<wxDownloadManagerList::TitleEntry&> wxDownloadManagerList::GetTitleEntry(long item)
|
||||
{
|
||||
long counter = 0;
|
||||
for (const auto& data : m_sorted_data)
|
||||
{
|
||||
if (!data.get().visible)
|
||||
continue;
|
||||
|
||||
if (item != counter++)
|
||||
continue;
|
||||
|
||||
return data.get().entry;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
boost::optional<const wxDownloadManagerList::TitleEntry&> wxDownloadManagerList::GetTitleEntry(long item) const
|
||||
{
|
||||
long counter = 0;
|
||||
for (const auto& data : m_sorted_data)
|
||||
{
|
||||
if (!data.get().visible)
|
||||
continue;
|
||||
|
||||
if (item != counter++)
|
||||
continue;
|
||||
|
||||
return data.get().entry;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::OnClose(wxCloseEvent& event)
|
||||
{
|
||||
event.Skip();
|
||||
// wait until all tasks are complete
|
||||
if (m_context_worker.valid())
|
||||
m_context_worker.get();
|
||||
g_mainFrame->RequestGameListRefresh(); // todo: add games instead of fully refreshing game list if a game is downloaded
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::OnColumnClick(wxListEvent& event)
|
||||
{
|
||||
const int column = event.GetColumn();
|
||||
|
||||
if (column == m_sort_by_column)
|
||||
{
|
||||
m_sort_less = !m_sort_less;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_sort_by_column = column;
|
||||
m_sort_less = true;
|
||||
}
|
||||
SortEntries();
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::RemoveItem(long item)
|
||||
{
|
||||
const int item_count = GetItemCount();
|
||||
|
||||
const ItemData* ref = nullptr;
|
||||
long counter = 0;
|
||||
for(auto it = m_sorted_data.begin(); it != m_sorted_data.end(); ++it)
|
||||
{
|
||||
if (!it->get().visible)
|
||||
continue;
|
||||
|
||||
if (item != counter++)
|
||||
continue;
|
||||
|
||||
ref = &(it->get());
|
||||
m_sorted_data.erase(it);
|
||||
break;
|
||||
}
|
||||
|
||||
// shouldn't happen
|
||||
if (ref == nullptr)
|
||||
return;
|
||||
|
||||
for(auto it = m_data.begin(); it != m_data.end(); ++it)
|
||||
{
|
||||
if (ref != (*it).get())
|
||||
continue;
|
||||
|
||||
m_data.erase(it);
|
||||
break;
|
||||
}
|
||||
|
||||
SetItemCount(std::max(0, item_count - 1));
|
||||
RefreshPage();
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::RemoveItem(const TitleEntry& entry)
|
||||
{
|
||||
const int item_count = GetItemCount();
|
||||
|
||||
const TitleEntry* ref = &entry;
|
||||
for (auto it = m_sorted_data.begin(); it != m_sorted_data.end(); ++it)
|
||||
{
|
||||
if (ref != &it->get().entry)
|
||||
continue;
|
||||
|
||||
m_sorted_data.erase(it);
|
||||
break;
|
||||
}
|
||||
|
||||
for (auto it = m_data.begin(); it != m_data.end(); ++it)
|
||||
{
|
||||
if (ref != &(*it).get()->entry)
|
||||
continue;
|
||||
|
||||
m_data.erase(it);
|
||||
break;
|
||||
}
|
||||
|
||||
SetItemCount(std::max(0, item_count - 1));
|
||||
RefreshPage();
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::OnItemSelected(wxListEvent& event)
|
||||
{
|
||||
event.Skip();
|
||||
m_tooltip_timer->Stop();
|
||||
const auto selection = event.GetIndex();
|
||||
|
||||
if (selection == wxNOT_FOUND)
|
||||
{
|
||||
m_tooltip_window->Hide();
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum ContextMenuEntries
|
||||
{
|
||||
kContextMenuRetry,
|
||||
kContextMenuDownload,
|
||||
kContextMenuPause,
|
||||
kContextMenuResume,
|
||||
};
|
||||
void wxDownloadManagerList::OnContextMenu(wxContextMenuEvent& event)
|
||||
{
|
||||
// still doing work
|
||||
if (m_context_worker.valid() && !future_is_ready(m_context_worker))
|
||||
return;
|
||||
|
||||
wxMenu menu;
|
||||
menu.Bind(wxEVT_COMMAND_MENU_SELECTED, &wxDownloadManagerList::OnContextMenuSelected, this);
|
||||
|
||||
const auto selection = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
|
||||
if (selection == wxNOT_FOUND)
|
||||
return;
|
||||
|
||||
const auto entry = GetTitleEntry(selection);
|
||||
if (!entry.has_value())
|
||||
return;
|
||||
|
||||
if (entry->isPaused)
|
||||
menu.Append(kContextMenuResume, _("&Resume"));
|
||||
else if (entry->status == TitleDownloadStatus::Error)
|
||||
menu.Append(kContextMenuRetry, _("&Retry"));
|
||||
else if(entry->status == TitleDownloadStatus::Available)
|
||||
menu.Append(kContextMenuDownload, _("&Download"));
|
||||
//else if(entry->status == TitleDownloadStatus::Downloading || entry->status == TitleDownloadStatus::Initializing)
|
||||
// menu.Append(kContextMenuPause, _("&Pause")); buggy!
|
||||
|
||||
PopupMenu(&menu);
|
||||
|
||||
// TODO: fix tooltip position
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::SetCurrentDownloadMgr(DownloadManager* dlMgr)
|
||||
{
|
||||
std::unique_lock<std::mutex> _l(m_dlMgrMutex);
|
||||
m_dlMgr = dlMgr;
|
||||
}
|
||||
|
||||
bool wxDownloadManagerList::StartDownloadEntry(const TitleEntry& entry)
|
||||
{
|
||||
std::unique_lock<std::mutex> _l(m_dlMgrMutex);
|
||||
if (m_dlMgr)
|
||||
m_dlMgr->initiateDownload(entry.titleId, entry.version);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wxDownloadManagerList::RetryDownloadEntry(const TitleEntry& entry)
|
||||
{
|
||||
StartDownloadEntry(entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wxDownloadManagerList::PauseDownloadEntry(const TitleEntry& entry)
|
||||
{
|
||||
std::unique_lock<std::mutex> _l(m_dlMgrMutex);
|
||||
if (m_dlMgr)
|
||||
m_dlMgr->pauseDownload(entry.titleId, entry.version);
|
||||
return true;
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::OnContextMenuSelected(wxCommandEvent& event)
|
||||
{
|
||||
// still doing work
|
||||
if (m_context_worker.valid() && !future_is_ready(m_context_worker))
|
||||
return;
|
||||
|
||||
const auto selection = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
|
||||
if (selection == wxNOT_FOUND)
|
||||
return;
|
||||
|
||||
const auto entry = GetTitleEntry(selection);
|
||||
if (!entry.has_value())
|
||||
return;
|
||||
|
||||
switch (event.GetId())
|
||||
{
|
||||
case kContextMenuDownload:
|
||||
StartDownloadEntry(entry.value());
|
||||
break;
|
||||
case kContextMenuRetry:
|
||||
RetryDownloadEntry(entry.value());
|
||||
break;
|
||||
case kContextMenuResume:
|
||||
StartDownloadEntry(entry.value());
|
||||
break;
|
||||
case kContextMenuPause:
|
||||
PauseDownloadEntry(entry.value());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::OnTimer(wxTimerEvent& event)
|
||||
{
|
||||
if(event.GetTimer().GetId() != m_tooltip_timer->GetId())
|
||||
{
|
||||
event.Skip();
|
||||
return;
|
||||
}
|
||||
|
||||
m_tooltip_window->Show();
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::OnRemoveItem(wxCommandEvent& event)
|
||||
{
|
||||
RemoveItem(event.GetInt());
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::OnRemoveEntry(wxCommandEvent& event)
|
||||
{
|
||||
wxASSERT(event.GetClientData() != nullptr);
|
||||
RemoveItem(*(TitleEntry*)event.GetClientData());
|
||||
}
|
||||
|
||||
wxString wxDownloadManagerList::GetTitleEntryText(const TitleEntry& entry, ItemColumn column)
|
||||
{
|
||||
switch (column)
|
||||
{
|
||||
case ColumnTitleId:
|
||||
return wxStringFormat2("{:08x}-{:08x}", (uint32)(entry.titleId >> 32), (uint32)(entry.titleId & 0xFFFFFFFF));
|
||||
case ColumnName:
|
||||
{
|
||||
return entry.name;
|
||||
}
|
||||
case ColumnType:
|
||||
return wxStringFormat2("{}", entry.type);
|
||||
case ColumnVersion:
|
||||
{
|
||||
// dont show version for base game unless it is not v0
|
||||
if (entry.type == EntryType::Base && entry.version == 0)
|
||||
return "";
|
||||
if (entry.type == EntryType::DLC && entry.version == 0)
|
||||
return "";
|
||||
return wxStringFormat2("v{}", entry.version);
|
||||
}
|
||||
case ColumnProgress:
|
||||
{
|
||||
if (entry.status == TitleDownloadStatus::Downloading)
|
||||
{
|
||||
if (entry.progress >= 1000)
|
||||
return "100%";
|
||||
return wxStringFormat2("{:.1f}%", (float)entry.progress / 10.0f); // one decimal
|
||||
}
|
||||
else if (entry.status == TitleDownloadStatus::Installing || entry.status == TitleDownloadStatus::Checking || entry.status == TitleDownloadStatus::Verifying)
|
||||
{
|
||||
return wxStringFormat2("{0}/{1}", entry.progress, entry.progressMax); // number of processed files/content files
|
||||
}
|
||||
return "";
|
||||
}
|
||||
case ColumnStatus:
|
||||
{
|
||||
if (entry.isPaused)
|
||||
return _("Paused");
|
||||
else if (entry.status == TitleDownloadStatus::Available)
|
||||
{
|
||||
if (entry.progress == 1)
|
||||
return _("Not installed (Partially downloaded)");
|
||||
if (entry.progress == 2)
|
||||
return _("Update available");
|
||||
return _("Not installed");
|
||||
}
|
||||
else if (entry.status == TitleDownloadStatus::Initializing)
|
||||
return _("Initializing");
|
||||
else if (entry.status == TitleDownloadStatus::Checking)
|
||||
return _("Checking");
|
||||
else if (entry.status == TitleDownloadStatus::Queued)
|
||||
return _("Queued");
|
||||
else if (entry.status == TitleDownloadStatus::Downloading)
|
||||
return _("Downloading");
|
||||
else if (entry.status == TitleDownloadStatus::Verifying)
|
||||
return _("Verifying");
|
||||
else if (entry.status == TitleDownloadStatus::Installing)
|
||||
return _("Installing");
|
||||
else if (entry.status == TitleDownloadStatus::Installed)
|
||||
return _("Installed");
|
||||
else if (entry.status == TitleDownloadStatus::Error)
|
||||
{
|
||||
wxString errorStatusMsg = _("Error:");
|
||||
errorStatusMsg.append(" ");
|
||||
errorStatusMsg.append(entry.errorMsg);
|
||||
return errorStatusMsg;
|
||||
}
|
||||
else
|
||||
return "Unknown status";
|
||||
}
|
||||
}
|
||||
|
||||
return wxEmptyString;
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::AddOrUpdateTitle(TitleEntryData_t* obj)
|
||||
{
|
||||
const auto& data = obj->GetData();
|
||||
// if already in list only update
|
||||
auto entry = GetTitleEntry(data.titleId, data.version);
|
||||
if (entry.has_value())
|
||||
{
|
||||
// update item
|
||||
entry.value() = data;
|
||||
RefreshPage(); // more efficient way to do this?
|
||||
return;
|
||||
}
|
||||
|
||||
m_data.emplace_back(std::make_unique<ItemData>(true, data));
|
||||
m_sorted_data.emplace_back(*m_data[m_data.size() - 1]);
|
||||
SetItemCount(m_data.size());
|
||||
|
||||
// reapply sort
|
||||
Filter2(m_filterShowTitles, m_filterShowUpdates, m_filterShowInstalled);
|
||||
SortEntries();
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::UpdateTitleStatusDepr(TitleEntryData_t* obj, const wxString& text)
|
||||
{
|
||||
const auto& data = obj->GetData();
|
||||
const auto entry = GetTitleEntry(data.titleId, data.version);
|
||||
// check if already added to list
|
||||
if (!entry.has_value())
|
||||
return;
|
||||
|
||||
// update gamelist text
|
||||
for(size_t i = 0; i < m_sorted_data.size(); ++i)
|
||||
{
|
||||
if (m_sorted_data[i].get().entry == data)
|
||||
{
|
||||
SetItem(i, ColumnStatus, text);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
forceLogDebug_printf("cant update title status of %llx", data.titleId);
|
||||
}
|
||||
|
||||
int wxDownloadManagerList::AddImage(const wxImage& image) const
|
||||
{
|
||||
return -1; // m_image_list->Add(image.Scale(kListIconWidth, kListIconWidth, wxIMAGE_QUALITY_BICUBIC));
|
||||
}
|
||||
|
||||
// return <
|
||||
bool wxDownloadManagerList::SortFunc(std::span<int> sortColumnOrder, const Type_t& v1, const Type_t& v2)
|
||||
{
|
||||
cemu_assert_debug(sortColumnOrder.size() == 5);
|
||||
|
||||
// visible have always priority
|
||||
if (!v1.get().visible && v2.get().visible)
|
||||
return false;
|
||||
else if (v1.get().visible && !v2.get().visible)
|
||||
return true;
|
||||
|
||||
const auto& entry1 = v1.get().entry;
|
||||
const auto& entry2 = v2.get().entry;
|
||||
|
||||
for (size_t i = 0; i < sortColumnOrder.size(); i++)
|
||||
{
|
||||
int sortByColumn = sortColumnOrder[i];
|
||||
if (sortByColumn == ColumnTitleId)
|
||||
{
|
||||
// ensure strong ordering -> use type since only one entry should be now (should be changed if every save for every user is displayed spearately?)
|
||||
if (entry1.titleId != entry2.titleId)
|
||||
return entry1.titleId < entry2.titleId;
|
||||
}
|
||||
else if (sortByColumn == ColumnName)
|
||||
{
|
||||
const int tmp = entry1.name.CmpNoCase(entry2.name);
|
||||
if (tmp != 0)
|
||||
return tmp < 0;
|
||||
}
|
||||
else if (sortByColumn == ColumnType)
|
||||
{
|
||||
if (std::underlying_type_t<EntryType>(entry1.type) != std::underlying_type_t<EntryType>(entry2.type))
|
||||
return std::underlying_type_t<EntryType>(entry1.type) < std::underlying_type_t<EntryType>(entry2.type);
|
||||
}
|
||||
else if (sortByColumn == ColumnVersion)
|
||||
{
|
||||
if (entry1.version != entry2.version)
|
||||
return entry1.version < entry2.version;
|
||||
}
|
||||
else if (sortByColumn == ColumnProgress)
|
||||
{
|
||||
if (entry1.progress != entry2.progress)
|
||||
return entry1.progress < entry2.progress;
|
||||
}
|
||||
else
|
||||
{
|
||||
cemu_assert_debug(false);
|
||||
return (uintptr_t)&entry1 < (uintptr_t)&entry2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
void wxDownloadManagerList::SortEntries()
|
||||
{
|
||||
boost::container::small_vector<int, 12> s_SortColumnOrder{ ColumnName, ColumnType, ColumnVersion, ColumnTitleId, ColumnProgress };
|
||||
|
||||
if (m_sort_by_column != -1)
|
||||
{
|
||||
// prioritize column by moving it to first position in the column sort order list
|
||||
s_SortColumnOrder.erase(std::remove(s_SortColumnOrder.begin(), s_SortColumnOrder.end(), m_sort_by_column), s_SortColumnOrder.end());
|
||||
s_SortColumnOrder.insert(s_SortColumnOrder.begin(), m_sort_by_column);
|
||||
}
|
||||
|
||||
std::sort(m_sorted_data.begin(), m_sorted_data.end(),
|
||||
[this, &s_SortColumnOrder](const Type_t& v1, const Type_t& v2) -> bool
|
||||
{
|
||||
const bool result = SortFunc({ s_SortColumnOrder.data(), s_SortColumnOrder.size() }, v1, v2);
|
||||
return m_sort_less ? result : !result;
|
||||
});
|
||||
|
||||
RefreshPage();
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::RefreshPage() { RefreshItems(GetTopItem(), GetTopItem() + GetCountPerPage() + 1); }
|
||||
|
||||
int wxDownloadManagerList::Filter(const wxString& filter, const wxString& prefix, ItemColumn column)
|
||||
{
|
||||
if (prefix.empty())
|
||||
return -1;
|
||||
|
||||
if (!filter.StartsWith(prefix))
|
||||
return -1;
|
||||
|
||||
int counter = 0;
|
||||
const auto tmp_filter = filter.substr(prefix.size() - 1).Trim(false);
|
||||
for (auto&& data : m_data)
|
||||
{
|
||||
if (GetTitleEntryText(data->entry, column).Upper().Contains(tmp_filter))
|
||||
{
|
||||
data->visible = true;
|
||||
++counter;
|
||||
}
|
||||
else
|
||||
data->visible = false;
|
||||
}
|
||||
return counter;
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::Filter(const wxString& filter)
|
||||
{
|
||||
if(filter.empty())
|
||||
{
|
||||
std::for_each(m_data.begin(), m_data.end(), [](ItemDataPtr& data) { data->visible = true; });
|
||||
SetItemCount(m_data.size());
|
||||
RefreshPage();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto filter_upper = filter.Upper().Trim(false).Trim(true);
|
||||
int counter = 0;
|
||||
|
||||
if (const auto result = Filter(filter_upper, "TITLEID:", ColumnTitleId) != -1)
|
||||
counter = result;
|
||||
else if (const auto result = Filter(filter_upper, "NAME:", ColumnName) != -1)
|
||||
counter = result;
|
||||
else if (const auto result = Filter(filter_upper, "TYPE:", ColumnType) != -1)
|
||||
counter = result;
|
||||
//else if (const auto result = Filter(filter_upper, "REGION:", ColumnRegion) != -1)
|
||||
// counter = result;
|
||||
else if (const auto result = Filter(filter_upper, "VERSION:", ColumnVersion) != -1)
|
||||
counter = result;
|
||||
else if(filter_upper == "ERROR")
|
||||
{
|
||||
for (auto&& data : m_data)
|
||||
{
|
||||
bool visible = false;
|
||||
//if (data->entry.error != TitleError::None)
|
||||
// visible = true;
|
||||
|
||||
data->visible = visible;
|
||||
if (visible)
|
||||
++counter;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto&& data : m_data)
|
||||
{
|
||||
bool visible = false;
|
||||
if (data->entry.name.Upper().Contains(filter_upper))
|
||||
visible = true;
|
||||
else if (GetTitleEntryText(data->entry, ColumnTitleId).Upper().Contains(filter_upper))
|
||||
visible = true;
|
||||
else if (GetTitleEntryText(data->entry, ColumnType).Upper().Contains(filter_upper))
|
||||
visible = true;
|
||||
|
||||
data->visible = visible;
|
||||
if (visible)
|
||||
++counter;
|
||||
}
|
||||
}
|
||||
|
||||
SetItemCount(counter);
|
||||
RefreshPage();
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::Filter2(bool showTitles, bool showUpdates, bool showInstalled)
|
||||
{
|
||||
m_filterShowTitles = showTitles;
|
||||
m_filterShowUpdates = showUpdates;
|
||||
m_filterShowInstalled = showInstalled;
|
||||
if (showTitles && showUpdates && showInstalled)
|
||||
{
|
||||
std::for_each(m_data.begin(), m_data.end(), [](ItemDataPtr& data) { data->visible = true; });
|
||||
SetItemCount(m_data.size());
|
||||
RefreshPage();
|
||||
return;
|
||||
}
|
||||
|
||||
size_t counter = 0;
|
||||
|
||||
for (auto&& data : m_data)
|
||||
{
|
||||
bool visible = false;
|
||||
|
||||
TitleIdParser tParser(data->entry.titleId);
|
||||
bool isInstalled = data->entry.status == TitleDownloadStatus::Installed;
|
||||
if (tParser.IsBaseTitleUpdate())
|
||||
{
|
||||
if (showUpdates && (showInstalled || !isInstalled))
|
||||
visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (showTitles && (showInstalled || !isInstalled))
|
||||
visible = true;
|
||||
}
|
||||
|
||||
data->visible = visible;
|
||||
if (visible)
|
||||
++counter;
|
||||
}
|
||||
|
||||
SetItemCount(counter);
|
||||
RefreshPage();
|
||||
}
|
||||
|
||||
size_t wxDownloadManagerList::GetCountByType(EntryType type) const
|
||||
{
|
||||
size_t result = 0;
|
||||
for(const auto& data : m_data)
|
||||
{
|
||||
if (data->entry.type == type)
|
||||
++result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::ClearItems()
|
||||
{
|
||||
m_sorted_data.clear();
|
||||
m_data.clear();
|
||||
SetItemCount(0);
|
||||
RefreshPage();
|
||||
}
|
||||
|
||||
void wxDownloadManagerList::AutosizeColumns()
|
||||
{
|
||||
wxAutosizeColumns(this, ColumnTitleId, ColumnMAX - 1);
|
||||
}
|
||||
|
||||
174
src/gui/components/wxDownloadManagerList.h
Normal file
174
src/gui/components/wxDownloadManagerList.h
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
#pragma once
|
||||
|
||||
#include "gui/helpers/wxCustomData.h"
|
||||
#include "config/CemuConfig.h"
|
||||
|
||||
#include <wx/listctrl.h>
|
||||
|
||||
#include <boost/optional.hpp> // std::optional doesn't support optional reference inner types yet
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class wxDownloadManagerList : public wxListCtrl
|
||||
{
|
||||
friend class TitleManager;
|
||||
public:
|
||||
wxDownloadManagerList(wxWindow* parent, wxWindowID id = wxID_ANY);
|
||||
|
||||
enum ItemColumn
|
||||
{
|
||||
ColumnTitleId = 0,
|
||||
ColumnName,
|
||||
ColumnVersion,
|
||||
ColumnType,
|
||||
ColumnProgress,
|
||||
ColumnStatus,
|
||||
|
||||
ColumnMAX,
|
||||
};
|
||||
|
||||
enum class EntryType
|
||||
{
|
||||
Base,
|
||||
Update,
|
||||
DLC,
|
||||
};
|
||||
|
||||
enum class TitleDownloadStatus
|
||||
{
|
||||
None,
|
||||
Available, // available for download
|
||||
Error, // error state
|
||||
Queued, // queued for download
|
||||
Initializing, // downloading/parsing TMD
|
||||
Checking, // checking for previously downloaded files
|
||||
Downloading,
|
||||
Verifying, // verifying downloaded files
|
||||
Installing,
|
||||
Installed,
|
||||
// error state?
|
||||
};
|
||||
|
||||
void SortEntries();
|
||||
void RefreshPage();
|
||||
void Filter(const wxString& filter);
|
||||
void Filter2(bool showTitles, bool showUpdates, bool showInstalled);
|
||||
[[nodiscard]] size_t GetCountByType(EntryType type) const;
|
||||
void ClearItems();
|
||||
void AutosizeColumns();
|
||||
|
||||
struct TitleEntry
|
||||
{
|
||||
TitleEntry(const EntryType& type, bool isPackage, uint64 titleId, uint16 version, bool isPaused)
|
||||
: type(type), isPackage(isPackage), titleId(titleId), version(version), isPaused(isPaused) {}
|
||||
|
||||
EntryType type;
|
||||
|
||||
bool isPaused{};
|
||||
int icon = -1;
|
||||
bool isPackage;
|
||||
uint64 titleId;
|
||||
wxString name;
|
||||
uint32_t version{ 0 };
|
||||
uint32 progress; // downloading: in 1/10th of a percent, installing: number of files
|
||||
uint32 progressMax{ 0 };
|
||||
CafeConsoleRegion region;
|
||||
|
||||
TitleDownloadStatus status = TitleDownloadStatus::None;
|
||||
std::string errorMsg;
|
||||
|
||||
bool operator==(const TitleEntry& e) const
|
||||
{
|
||||
return type == e.type && titleId == e.titleId && version == e.version;
|
||||
}
|
||||
bool operator!=(const TitleEntry& e) const { return !(*this == e); }
|
||||
};
|
||||
boost::optional<const TitleEntry&> GetSelectedTitleEntry() const;
|
||||
|
||||
private:
|
||||
void AddColumns();
|
||||
int Filter(const wxString& filter, const wxString& prefix, ItemColumn column);
|
||||
boost::optional<TitleEntry&> GetSelectedTitleEntry();
|
||||
boost::optional<TitleEntry&> GetTitleEntry(uint64 titleId, uint16 titleVersion);
|
||||
|
||||
class wxPanel* m_tooltip_window;
|
||||
class wxStaticText* m_tooltip_text;
|
||||
class wxTimer* m_tooltip_timer;
|
||||
|
||||
void OnClose(wxCloseEvent& event);
|
||||
void OnColumnClick(wxListEvent& event);
|
||||
void OnContextMenu(wxContextMenuEvent& event);
|
||||
void OnItemSelected(wxListEvent& event);
|
||||
void OnContextMenuSelected(wxCommandEvent& event);
|
||||
void OnTimer(class wxTimerEvent& event);
|
||||
void OnRemoveItem(wxCommandEvent& event);
|
||||
void OnRemoveEntry(wxCommandEvent& event);
|
||||
|
||||
using TitleEntryData_t = wxCustomData<TitleEntry>;
|
||||
void AddOrUpdateTitle(TitleEntryData_t* obj);
|
||||
void UpdateTitleStatusDepr(TitleEntryData_t* obj, const wxString& text);
|
||||
int AddImage(const wxImage& image) const;
|
||||
wxString OnGetItemText(long item, long column) const override;
|
||||
wxItemAttr* OnGetItemAttr(long item) const override;
|
||||
[[nodiscard]] boost::optional<const TitleEntry&> GetTitleEntry(long item) const;
|
||||
[[nodiscard]] boost::optional<TitleEntry&> GetTitleEntry(long item);
|
||||
//[[nodiscard]] boost::optional<const TitleEntry&> GetTitleEntry(const fs::path& path) const;
|
||||
//[[nodiscard]] boost::optional<TitleEntry&> GetTitleEntry(const fs::path& path);
|
||||
|
||||
void SetCurrentDownloadMgr(class DownloadManager* dlMgr);
|
||||
|
||||
//bool FixEntry(TitleEntry& entry);
|
||||
//bool VerifyEntryFiles(TitleEntry& entry);
|
||||
bool StartDownloadEntry(const TitleEntry& entry);
|
||||
bool RetryDownloadEntry(const TitleEntry& entry);
|
||||
bool PauseDownloadEntry(const TitleEntry& entry);
|
||||
|
||||
void RemoveItem(long item);
|
||||
void RemoveItem(const TitleEntry& entry);
|
||||
|
||||
struct ItemData
|
||||
{
|
||||
ItemData(bool visible, const TitleEntry& entry)
|
||||
: visible(visible), entry(entry) {}
|
||||
|
||||
bool visible;
|
||||
TitleEntry entry;
|
||||
};
|
||||
using ItemDataPtr = std::unique_ptr<ItemData>;
|
||||
std::vector<ItemDataPtr> m_data;
|
||||
std::vector<std::reference_wrapper<ItemData>> m_sorted_data;
|
||||
|
||||
int m_sort_by_column = ItemColumn::ColumnName;
|
||||
bool m_sort_less = true;
|
||||
|
||||
bool m_filterShowTitles = true;
|
||||
bool m_filterShowUpdates = true;
|
||||
bool m_filterShowInstalled = true;
|
||||
DownloadManager* m_dlMgr{};
|
||||
std::mutex m_dlMgrMutex;
|
||||
using Type_t = std::reference_wrapper<const ItemData>;
|
||||
bool SortFunc(std::span<int> sortColumnOrder, const Type_t& v1, const Type_t& v2);
|
||||
|
||||
static wxString GetTitleEntryText(const TitleEntry& entry, ItemColumn column);
|
||||
std::future<bool> m_context_worker;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<wxDownloadManagerList::EntryType> : formatter<string_view>
|
||||
{
|
||||
using base = fmt::formatter<fmt::string_view>;
|
||||
template <typename FormatContext>
|
||||
auto format(const wxDownloadManagerList::EntryType& type, FormatContext& ctx)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case wxDownloadManagerList::EntryType::Base:
|
||||
return base::format("base", ctx);
|
||||
case wxDownloadManagerList::EntryType::Update:
|
||||
return base::format("update", ctx);
|
||||
case wxDownloadManagerList::EntryType::DLC:
|
||||
return base::format("DLC", ctx);
|
||||
}
|
||||
return base::format(std::to_string(static_cast<std::underlying_type_t<wxDownloadManagerList::EntryType>>(type)), ctx);
|
||||
}
|
||||
};
|
||||
1098
src/gui/components/wxGameList.cpp
Normal file
1098
src/gui/components/wxGameList.cpp
Normal file
File diff suppressed because it is too large
Load diff
148
src/gui/components/wxGameList.h
Normal file
148
src/gui/components/wxGameList.h
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
#pragma once
|
||||
|
||||
#include "config/CemuConfig.h"
|
||||
#include "Cafe/TitleList/TitleId.h"
|
||||
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
|
||||
#include <wx/listctrl.h>
|
||||
#include <wx/timer.h>
|
||||
#include <wx/panel.h>
|
||||
#include "util/helpers/Semaphore.h"
|
||||
|
||||
class wxTitleIdEvent : public wxCommandEvent
|
||||
{
|
||||
public:
|
||||
wxTitleIdEvent(int type, uint64 title_id)
|
||||
: wxCommandEvent(type), m_title_id(title_id) {}
|
||||
|
||||
[[nodiscard]] uint64 GetTitleId() const { return m_title_id; }
|
||||
|
||||
private:
|
||||
uint64 m_title_id;
|
||||
};
|
||||
|
||||
wxDECLARE_EVENT(wxEVT_OPEN_SETTINGS, wxCommandEvent);
|
||||
wxDECLARE_EVENT(wxEVT_OPEN_GRAPHIC_PACK, wxTitleIdEvent);
|
||||
wxDECLARE_EVENT(wxEVT_GAMELIST_BEGIN_UPDATE, wxCommandEvent);
|
||||
wxDECLARE_EVENT(wxEVT_GAMELIST_END_UPDATE, wxCommandEvent);
|
||||
|
||||
class wxGameList : public wxListCtrl
|
||||
{
|
||||
friend class MainWindow;
|
||||
public:
|
||||
enum class Style
|
||||
{
|
||||
kList,
|
||||
kIcons,
|
||||
kSmallIcons
|
||||
};
|
||||
|
||||
wxGameList(wxWindow* parent, wxWindowID id = wxID_ANY);
|
||||
~wxGameList();
|
||||
|
||||
void SetStyle(Style style, bool save = true);
|
||||
|
||||
void LoadConfig();
|
||||
void SaveConfig(bool flush = false);
|
||||
bool IsVisible(long item) const; // only available in wxwidgets 3.1.3
|
||||
|
||||
void ReloadGameEntries(bool cached = false);
|
||||
|
||||
long FindListItemByTitleId(uint64 title_id) const;
|
||||
void OnClose(wxCloseEvent& event);
|
||||
|
||||
private:
|
||||
std::atomic_bool m_exit = false;
|
||||
Style m_style;
|
||||
long GetStyleFlags(Style style) const;
|
||||
|
||||
inline static const wxColour kUpdateColor{ 0x3939c3 };
|
||||
inline static const wxColour kFavoriteColor{ 0xD3F6FD };
|
||||
inline static const wxColour kSecondColor{ 0xFDF9F2 };
|
||||
void UpdateItemColors(sint32 startIndex = 0);
|
||||
|
||||
enum ItemColumns
|
||||
{
|
||||
ColumnHiddenName = 0,
|
||||
ColumnIcon,
|
||||
ColumnName,
|
||||
ColumnVersion,
|
||||
ColumnDLC,
|
||||
ColumnGameTime,
|
||||
ColumnGameStarted,
|
||||
ColumnRegion,
|
||||
ColumnFavorite
|
||||
};
|
||||
|
||||
int s_last_column = ColumnName;
|
||||
int s_direction = 1;
|
||||
void SortEntries(int column = -1);
|
||||
struct SortData
|
||||
{
|
||||
wxGameList* thisptr;
|
||||
int column;
|
||||
int dir;
|
||||
};
|
||||
|
||||
int FindInsertPosition(TitleId titleId);
|
||||
int SortComparator(uint64 titleId1, uint64 titleId2, SortData* sortData);
|
||||
static int SortFunction(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData);
|
||||
|
||||
wxTimer* m_tooltip_timer;
|
||||
wxToolTip* m_tool_tip;
|
||||
wxPanel* m_tooltip_window;
|
||||
wxPoint m_mouse_position;
|
||||
|
||||
std::mutex m_async_worker_mutex;
|
||||
std::thread m_async_worker_thread;
|
||||
CounterSemaphore m_async_task_count;
|
||||
std::atomic_bool m_async_worker_active{false};
|
||||
|
||||
std::vector<TitleId> m_icon_load_queue;
|
||||
|
||||
uint64 m_callbackIdTitleList;
|
||||
|
||||
std::string GetNameByTitleId(uint64 titleId);
|
||||
|
||||
void HandleTitleListCallback(struct CafeTitleListCallbackEvent* evt);
|
||||
|
||||
void AsyncWorkerThread();
|
||||
void RequestLoadIconAsync(TitleId titleId);
|
||||
bool QueryIconForTitle(TitleId titleId, int& icon, int& iconSmall);
|
||||
|
||||
inline static constexpr int kListIconWidth = 64;
|
||||
inline static constexpr int kIconWidth = 128;
|
||||
wxImageList* m_image_list, *m_image_list_small;
|
||||
|
||||
std::mutex m_icon_cache_mtx;
|
||||
std::set<TitleId> m_icon_loaded;
|
||||
std::map<TitleId, std::pair<int, int>> m_icon_cache; // pair contains icon and small icon
|
||||
|
||||
std::map<TitleId, std::string> m_name_cache;
|
||||
|
||||
// list mode
|
||||
void CreateListColumns();
|
||||
|
||||
void OnColumnClick(wxListEvent& event);
|
||||
void OnColumnRightClick(wxListEvent& event);
|
||||
void ApplyGameListColumnWidths();
|
||||
void OnColumnBeginResize(wxListEvent& event);
|
||||
void OnColumnResize(wxListEvent& event);
|
||||
void OnColumnDrag(wxListEvent& event);
|
||||
|
||||
// generic events
|
||||
void OnKeyDown(wxListEvent& event);
|
||||
void OnContextMenu(wxContextMenuEvent& event);
|
||||
void OnContextMenuSelected(wxCommandEvent& event);
|
||||
void OnGameEntryUpdatedByTitleId(wxTitleIdEvent& event);
|
||||
void OnItemActivated(wxListEvent& event);
|
||||
void OnTimer(wxTimerEvent& event);
|
||||
void OnMouseMove(wxMouseEvent& event);
|
||||
void OnLeaveWindow(wxMouseEvent& event);
|
||||
|
||||
static inline std::once_flag s_launch_file_once;
|
||||
};
|
||||
|
||||
118
src/gui/components/wxInputDraw.cpp
Normal file
118
src/gui/components/wxInputDraw.cpp
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
#include "gui/components/wxInputDraw.h"
|
||||
|
||||
#include <wx/dcbuffer.h>
|
||||
|
||||
wxInputDraw::wxInputDraw(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size)
|
||||
: wxWindow(parent, id, pos, size, 0, wxPanelNameStr)
|
||||
{
|
||||
SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
Bind(wxEVT_PAINT, &wxInputDraw::OnPaintEvent, this);
|
||||
}
|
||||
|
||||
wxInputDraw::~wxInputDraw() { Unbind(wxEVT_PAINT, &wxInputDraw::OnPaintEvent, this); }
|
||||
|
||||
void wxInputDraw::OnPaintEvent(wxPaintEvent& event)
|
||||
{
|
||||
wxAutoBufferedPaintDC dc(this);
|
||||
OnRender(dc);
|
||||
}
|
||||
|
||||
void wxInputDraw::OnRender(wxDC& dc)
|
||||
{
|
||||
dc.Clear();
|
||||
|
||||
glm::vec2 position;
|
||||
const wxPen *black, *red, *grey;
|
||||
const wxBrush *black_brush, *red_brush, *grey_brush;
|
||||
if(IsEnabled())
|
||||
{
|
||||
position = m_position;
|
||||
|
||||
black = wxBLACK_PEN;
|
||||
red = wxRED_PEN;
|
||||
grey = wxGREY_PEN;
|
||||
|
||||
black_brush = wxBLACK_BRUSH;
|
||||
red_brush = wxRED_BRUSH;
|
||||
grey_brush = wxGREY_BRUSH;
|
||||
}
|
||||
else
|
||||
{
|
||||
position = {};
|
||||
black = red = wxMEDIUM_GREY_PEN;
|
||||
grey = wxLIGHT_GREY_PEN;
|
||||
|
||||
black_brush = red_brush = wxMEDIUM_GREY_BRUSH;
|
||||
grey_brush = wxLIGHT_GREY_BRUSH;
|
||||
}
|
||||
|
||||
dc.SetBackgroundMode(wxSOLID);
|
||||
dc.SetBackground(*wxWHITE_BRUSH);
|
||||
|
||||
const auto size = GetSize();
|
||||
const auto min_size = (float)std::min(size.GetWidth(), size.GetHeight()) - 1.0f;
|
||||
const Vector2f middle{min_size / 2.0f, min_size / 2.0f};
|
||||
|
||||
// border
|
||||
const wxRect border{0, 0, (int)min_size, (int)min_size};
|
||||
dc.SetPen(*black);
|
||||
dc.DrawRectangle(border);
|
||||
|
||||
dc.SetPen(IsEnabled() ? wxPen(wxColour(0x336600)) : *grey); // dark green
|
||||
dc.DrawCircle((int)middle.x, (int)middle.y, (int)middle.x);
|
||||
|
||||
if (m_deadzone > 0)
|
||||
{
|
||||
dc.SetPen(*grey);
|
||||
dc.SetBrush(*wxLIGHT_GREY_BRUSH);
|
||||
const auto deadzone_size = m_deadzone * min_size / 2.0f;
|
||||
dc.DrawCircle(
|
||||
static_cast<int>(middle.x),
|
||||
static_cast<int>(middle.x),
|
||||
(int)deadzone_size);
|
||||
|
||||
if (length(position) >= m_deadzone)
|
||||
{
|
||||
dc.SetPen(*red);
|
||||
dc.SetBrush(*red_brush);
|
||||
|
||||
if (std::abs(1.0f - length(position)) < 0.05f)
|
||||
{
|
||||
dc.SetPen(wxPen(wxColour(0x336600)));
|
||||
dc.SetBrush(wxColour(0x336600));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dc.SetPen(*black);
|
||||
dc.SetBrush(*black_brush);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dc.SetPen(*red);
|
||||
dc.SetBrush(*red_brush);
|
||||
}
|
||||
|
||||
// draw axis
|
||||
const wxPoint pos
|
||||
{
|
||||
static_cast<int>(middle.x + position.x * middle.x),
|
||||
static_cast<int>(middle.y - position.y * middle.y)
|
||||
};
|
||||
dc.DrawCircle(pos.x, pos.y, 2);
|
||||
|
||||
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
||||
}
|
||||
|
||||
void wxInputDraw::SetAxisValue(const glm::vec2& position)
|
||||
{
|
||||
m_position = position;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void wxInputDraw::SetDeadzone(float deadzone)
|
||||
{
|
||||
m_deadzone = deadzone;
|
||||
Refresh();
|
||||
}
|
||||
24
src/gui/components/wxInputDraw.h
Normal file
24
src/gui/components/wxInputDraw.h
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include "input/api/ControllerState.h"
|
||||
#include "util/math/vector2.h"
|
||||
|
||||
#include <wx/window.h>
|
||||
|
||||
|
||||
class wxInputDraw : public wxWindow
|
||||
{
|
||||
public:
|
||||
wxInputDraw(wxWindow *parent, wxWindowID id, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize);
|
||||
~wxInputDraw();
|
||||
|
||||
virtual void OnRender(wxDC& dc);
|
||||
void SetAxisValue(const glm::vec2& position);
|
||||
void SetDeadzone(float deadzone);
|
||||
|
||||
private:
|
||||
void OnPaintEvent(wxPaintEvent& event);
|
||||
|
||||
glm::vec2 m_position{};
|
||||
float m_deadzone{0};
|
||||
};
|
||||
160
src/gui/components/wxLogCtrl.cpp
Normal file
160
src/gui/components/wxLogCtrl.cpp
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
#include "gui/components/wxLogCtrl.h"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
wxDEFINE_EVENT(EVT_ON_LIST_UPDATED, wxEvent);
|
||||
|
||||
wxLogCtrl::wxLogCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
|
||||
: TextList(parent, id, pos, size, style)
|
||||
{
|
||||
m_timer = new wxTimer(this);
|
||||
this->Bind(wxEVT_TIMER, &wxLogCtrl::OnTimer, this);
|
||||
this->Bind(EVT_ON_LIST_UPDATED, &wxLogCtrl::OnActiveListUpdated, this);
|
||||
m_timer->Start(250);
|
||||
}
|
||||
|
||||
wxLogCtrl::~wxLogCtrl()
|
||||
{
|
||||
this->Unbind(wxEVT_TIMER, &wxLogCtrl::OnTimer, this);
|
||||
this->Unbind(EVT_ON_LIST_UPDATED, &wxLogCtrl::OnActiveListUpdated, this);
|
||||
|
||||
if(m_timer)
|
||||
m_timer->Stop();
|
||||
|
||||
if(m_update_worker.joinable())
|
||||
m_update_worker.join();
|
||||
}
|
||||
|
||||
void wxLogCtrl::SetActiveFilter(const std::string& active_filter)
|
||||
{
|
||||
if(m_active_filter == active_filter)
|
||||
return;
|
||||
|
||||
m_active_filter = active_filter;
|
||||
|
||||
if(m_update_worker.joinable())
|
||||
m_update_worker.join();
|
||||
|
||||
m_update_worker = std::thread(&wxLogCtrl::UpdateActiveEntries, this);
|
||||
}
|
||||
|
||||
const std::string& wxLogCtrl::GetActiveFilter() const
|
||||
{
|
||||
return m_active_filter;
|
||||
}
|
||||
|
||||
void wxLogCtrl::SetFilterMessage(bool state)
|
||||
{
|
||||
if(m_filter_messages == state)
|
||||
return;
|
||||
|
||||
m_filter_messages = state;
|
||||
|
||||
if(m_update_worker.joinable())
|
||||
m_update_worker.join();
|
||||
|
||||
m_update_worker = std::thread(&wxLogCtrl::UpdateActiveEntries, this);
|
||||
}
|
||||
|
||||
bool wxLogCtrl::GetFilterMessage() const
|
||||
{
|
||||
return m_filter_messages;
|
||||
}
|
||||
|
||||
void wxLogCtrl::PushEntry(const wxString& filter, const wxString& message)
|
||||
{
|
||||
std::unique_lock lock(m_mutex);
|
||||
m_log_entries.emplace_back(filter, message);
|
||||
ListIt_t it = m_log_entries.back();
|
||||
lock.unlock();
|
||||
|
||||
if(m_active_filter.empty() || filter == m_active_filter || (m_filter_messages && boost::icontains(message, m_active_filter)))
|
||||
{
|
||||
std::unique_lock active_lock(m_active_mutex);
|
||||
m_active_entries.emplace_back(std::cref(it));
|
||||
const auto entry_count = m_active_entries.size();
|
||||
active_lock.unlock();
|
||||
|
||||
if(entry_count <= m_elements_visible)
|
||||
{
|
||||
RefreshLine(entry_count - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void wxLogCtrl::OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position)
|
||||
{
|
||||
wxPoint position = start_position;
|
||||
|
||||
std::scoped_lock lock(m_active_mutex);
|
||||
auto it = std::next(m_active_entries.cbegin(), start);
|
||||
if(it == m_active_entries.cend())
|
||||
return;
|
||||
|
||||
for (sint32 i = 0; i <= count && it != m_active_entries.cend(); ++i, ++it)
|
||||
{
|
||||
wxColour background_colour;
|
||||
if((start + i) % 2 == 0)
|
||||
background_colour = COLOR_WHITE;
|
||||
else
|
||||
background_colour = 0xFFFDF9F2;
|
||||
|
||||
DrawLineBackground(dc, position, background_colour);
|
||||
|
||||
dc.SetTextForeground(COLOR_BLACK);
|
||||
dc.DrawText(it->get().second, position);
|
||||
|
||||
NextLine(position, &start_position);
|
||||
}
|
||||
}
|
||||
|
||||
void wxLogCtrl::OnTimer(wxTimerEvent& event)
|
||||
{
|
||||
std::unique_lock lock(m_active_mutex);
|
||||
const auto count = m_active_entries.size();
|
||||
if(count == m_element_count)
|
||||
return;
|
||||
|
||||
lock.unlock();
|
||||
SetElementCount(count);
|
||||
|
||||
if(m_scrolled_to_end)
|
||||
{
|
||||
Scroll(0, count);
|
||||
RefreshControl();
|
||||
}
|
||||
}
|
||||
|
||||
void wxLogCtrl::OnActiveListUpdated(wxEvent& event)
|
||||
{
|
||||
std::unique_lock lock(m_active_mutex);
|
||||
const auto count = m_active_entries.size();
|
||||
lock.unlock();
|
||||
|
||||
SetElementCount(count);
|
||||
RefreshControl();
|
||||
}
|
||||
|
||||
void wxLogCtrl::UpdateActiveEntries()
|
||||
{
|
||||
{
|
||||
std::scoped_lock lock(m_mutex, m_active_mutex);
|
||||
m_active_entries.clear();
|
||||
if(m_active_filter.empty())
|
||||
{
|
||||
for (const auto& it : m_log_entries)
|
||||
m_active_entries.emplace_back(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto& it : m_log_entries)
|
||||
{
|
||||
if(it.first == m_active_filter || (m_filter_messages && boost::icontains(it.second, m_active_filter)) )
|
||||
m_active_entries.emplace_back(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wxQueueEvent(this, new wxCommandEvent(EVT_ON_LIST_UPDATED));
|
||||
}
|
||||
|
||||
36
src/gui/components/wxLogCtrl.h
Normal file
36
src/gui/components/wxLogCtrl.h
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
#include "TextList.h"
|
||||
|
||||
class wxLogCtrl : public TextList
|
||||
{
|
||||
public:
|
||||
wxLogCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style);
|
||||
~wxLogCtrl();
|
||||
|
||||
void SetActiveFilter(const std::string& active_filter);
|
||||
[[nodiscard]] const std::string& GetActiveFilter() const;
|
||||
void SetFilterMessage(bool state);
|
||||
[[nodiscard]] bool GetFilterMessage() const;
|
||||
|
||||
void PushEntry(const wxString& filter, const wxString& message);
|
||||
|
||||
protected:
|
||||
void OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position);
|
||||
|
||||
private:
|
||||
void OnTimer(wxTimerEvent& event);
|
||||
void OnActiveListUpdated(wxEvent& event);
|
||||
|
||||
wxTimer* m_timer;
|
||||
|
||||
std::string m_active_filter;
|
||||
std::thread m_update_worker;
|
||||
bool m_filter_messages = false;
|
||||
|
||||
std::mutex m_mutex, m_active_mutex;
|
||||
using ListEntry_t = std::pair<wxString, wxString>;
|
||||
using ListIt_t = std::list<ListEntry_t>::const_reference;
|
||||
std::list<ListEntry_t> m_log_entries;
|
||||
std::list<std::reference_wrapper<const ListEntry_t>> m_active_entries;
|
||||
void UpdateActiveEntries();
|
||||
};
|
||||
1277
src/gui/components/wxTitleManagerList.cpp
Normal file
1277
src/gui/components/wxTitleManagerList.cpp
Normal file
File diff suppressed because it is too large
Load diff
162
src/gui/components/wxTitleManagerList.h
Normal file
162
src/gui/components/wxTitleManagerList.h
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
#pragma once
|
||||
|
||||
#include "gui/helpers/wxCustomData.h"
|
||||
#include "config/CemuConfig.h"
|
||||
|
||||
#include <wx/listctrl.h>
|
||||
|
||||
#include <boost/optional.hpp> // std::optional doesn't support optional reference inner types yet
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class wxTitleManagerList : public wxListCtrl
|
||||
{
|
||||
friend class TitleManager;
|
||||
public:
|
||||
wxTitleManagerList(wxWindow* parent, wxWindowID id = wxID_ANY);
|
||||
~wxTitleManagerList();
|
||||
|
||||
enum ItemColumn
|
||||
{
|
||||
ColumnTitleId = 0,
|
||||
ColumnName,
|
||||
ColumnType,
|
||||
ColumnVersion,
|
||||
ColumnRegion,
|
||||
ColumnFormat,
|
||||
|
||||
ColumnMAX,
|
||||
};
|
||||
|
||||
enum class EntryType
|
||||
{
|
||||
Base,
|
||||
Update,
|
||||
Dlc,
|
||||
Save,
|
||||
System,
|
||||
};
|
||||
|
||||
enum class EntryFormat
|
||||
{
|
||||
Folder,
|
||||
WUD,
|
||||
WUA,
|
||||
};
|
||||
|
||||
// sort by column, if -1 will sort by last column or default (=titleid)
|
||||
void SortEntries(int column);
|
||||
void RefreshPage();
|
||||
void Filter(const wxString& filter);
|
||||
[[nodiscard]] size_t GetCountByType(EntryType type) const;
|
||||
void ClearItems();
|
||||
void AutosizeColumns();
|
||||
|
||||
struct TitleEntry
|
||||
{
|
||||
TitleEntry(const EntryType& type, const EntryFormat& format, fs::path path)
|
||||
: type(type), format(format), path(std::move(path)) {}
|
||||
|
||||
EntryType type;
|
||||
EntryFormat format;
|
||||
fs::path path;
|
||||
|
||||
int icon = -1;
|
||||
uint64 location_uid;
|
||||
uint64 title_id;
|
||||
wxString name;
|
||||
uint32_t version = 0;
|
||||
CafeConsoleRegion region;
|
||||
|
||||
std::vector<uint32> persistent_ids; // only used for save
|
||||
};
|
||||
boost::optional<const TitleEntry&> GetSelectedTitleEntry() const;
|
||||
|
||||
private:
|
||||
void AddColumns();
|
||||
int Filter(const wxString& filter, const wxString& prefix, ItemColumn column);
|
||||
boost::optional<TitleEntry&> GetSelectedTitleEntry();
|
||||
boost::optional<TitleEntry&> GetTitleEntryByUID(uint64 uid);
|
||||
|
||||
class wxPanel* m_tooltip_window;
|
||||
class wxStaticText* m_tooltip_text;
|
||||
class wxTimer* m_tooltip_timer;
|
||||
|
||||
void OnClose(wxCloseEvent& event);
|
||||
void OnColumnClick(wxListEvent& event);
|
||||
void OnContextMenu(wxContextMenuEvent& event);
|
||||
void OnItemSelected(wxListEvent& event);
|
||||
void OnContextMenuSelected(wxCommandEvent& event);
|
||||
void OnTimer(class wxTimerEvent& event);
|
||||
void OnRemoveItem(wxCommandEvent& event);
|
||||
void OnRemoveEntry(wxCommandEvent& event);
|
||||
|
||||
void OnTitleDiscovered(wxCommandEvent& event);
|
||||
void OnTitleRemoved(wxCommandEvent& event);
|
||||
void HandleTitleListCallback(struct CafeTitleListCallbackEvent* evt);
|
||||
void HandleSaveListCallback(struct CafeSaveListCallbackEvent* evt);
|
||||
|
||||
using TitleEntryData_t = wxCustomData<TitleEntry>;
|
||||
void AddTitle(TitleEntryData_t* obj);
|
||||
int AddImage(const wxImage& image) const;
|
||||
wxString OnGetItemText(long item, long column) const override;
|
||||
wxItemAttr* OnGetItemAttr(long item) const override;
|
||||
[[nodiscard]] boost::optional<const TitleEntry&> GetTitleEntry(long item) const;
|
||||
[[nodiscard]] boost::optional<TitleEntry&> GetTitleEntry(long item);
|
||||
[[nodiscard]] boost::optional<const TitleEntry&> GetTitleEntry(const fs::path& path) const;
|
||||
[[nodiscard]] boost::optional<TitleEntry&> GetTitleEntry(const fs::path& path);
|
||||
|
||||
bool VerifyEntryFiles(TitleEntry& entry);
|
||||
void OnConvertToCompressedFormat(uint64 titleId);
|
||||
bool DeleteEntry(long index, const TitleEntry& entry);
|
||||
|
||||
void RemoveItem(long item);
|
||||
void RemoveItem(const TitleEntry& entry);
|
||||
|
||||
struct ItemData
|
||||
{
|
||||
ItemData(bool visible, const TitleEntry& entry)
|
||||
: visible(visible), entry(entry) {}
|
||||
|
||||
bool visible;
|
||||
TitleEntry entry;
|
||||
};
|
||||
using ItemDataPtr = std::unique_ptr<ItemData>;
|
||||
std::vector<ItemDataPtr> m_data;
|
||||
std::vector<std::reference_wrapper<ItemData>> m_sorted_data;
|
||||
|
||||
int m_last_column_sorted = -1;
|
||||
bool m_sort_less = true;
|
||||
using Type_t = std::reference_wrapper<const ItemData>;
|
||||
bool SortFunc(int column, const Type_t& v1, const Type_t& v2);
|
||||
|
||||
static wxString GetTitleEntryText(const TitleEntry& entry, ItemColumn column);
|
||||
std::future<bool> m_context_worker;
|
||||
|
||||
uint64 m_callbackIdTitleList;
|
||||
uint64 m_callbackIdSaveList;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<wxTitleManagerList::EntryType> : formatter<string_view>
|
||||
{
|
||||
using base = fmt::formatter<fmt::string_view>;
|
||||
template <typename FormatContext>
|
||||
auto format(const wxTitleManagerList::EntryType& type, FormatContext& ctx)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case wxTitleManagerList::EntryType::Base:
|
||||
return base::format("base", ctx);
|
||||
case wxTitleManagerList::EntryType::Update:
|
||||
return base::format("update", ctx);
|
||||
case wxTitleManagerList::EntryType::Dlc:
|
||||
return base::format("DLC", ctx);
|
||||
case wxTitleManagerList::EntryType::Save:
|
||||
return base::format("save", ctx);
|
||||
case wxTitleManagerList::EntryType::System:
|
||||
return base::format("system", ctx);
|
||||
}
|
||||
return base::format(std::to_string(static_cast<std::underlying_type_t<wxTitleManagerList::EntryType>>(type)), ctx);
|
||||
}
|
||||
};
|
||||
264
src/gui/debugger/BreakpointWindow.cpp
Normal file
264
src/gui/debugger/BreakpointWindow.cpp
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
#include "gui/wxgui.h"
|
||||
#include "gui/debugger/BreakpointWindow.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "gui/debugger/DebuggerWindow2.h"
|
||||
#include "gui/guiWrapper.h"
|
||||
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
|
||||
|
||||
#include "Cemu/ExpressionParser/ExpressionParser.h"
|
||||
|
||||
enum
|
||||
{
|
||||
MENU_ID_CREATE_MEM_BP_READ = 1,
|
||||
MENU_ID_CREATE_MEM_BP_WRITE,
|
||||
|
||||
};
|
||||
|
||||
enum ItemColumns
|
||||
{
|
||||
ColumnEnabled = 0,
|
||||
ColumnAddress,
|
||||
ColumnType,
|
||||
ColumnComment,
|
||||
};
|
||||
|
||||
BreakpointWindow::BreakpointWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size)
|
||||
: wxFrame(&parent, wxID_ANY, "Breakpoints", wxDefaultPosition, wxSize(420, 250), wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxFRAME_FLOAT_ON_PARENT)
|
||||
{
|
||||
this->SetSizeHints(wxDefaultSize, wxDefaultSize);
|
||||
|
||||
this->wxWindowBase::SetBackgroundColour(*wxWHITE);
|
||||
|
||||
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_breakpoints = new wxCheckedListCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT);
|
||||
|
||||
|
||||
wxListItem col0;
|
||||
col0.SetId(ColumnEnabled);
|
||||
col0.SetText(_("On"));
|
||||
col0.SetWidth(32);
|
||||
m_breakpoints->InsertColumn(ColumnEnabled, col0);
|
||||
|
||||
wxListItem col1;
|
||||
col1.SetId(ColumnAddress);
|
||||
col1.SetText(_("Address"));
|
||||
col1.SetWidth(75);
|
||||
m_breakpoints->InsertColumn(ColumnAddress, col1);
|
||||
|
||||
wxListItem col2;
|
||||
col2.SetId(ColumnType);
|
||||
col2.SetText(_("Type"));
|
||||
col2.SetWidth(42);
|
||||
m_breakpoints->InsertColumn(ColumnType, col2);
|
||||
|
||||
wxListItem col3;
|
||||
col3.SetId(ColumnComment);
|
||||
col3.SetText(_("Comment"));
|
||||
col3.SetWidth(250);
|
||||
m_breakpoints->InsertColumn(ColumnComment, col3);
|
||||
|
||||
main_sizer->Add(m_breakpoints, 1, wxEXPAND);
|
||||
|
||||
this->SetSizer(main_sizer);
|
||||
this->wxWindowBase::Layout();
|
||||
|
||||
this->Centre(wxBOTH);
|
||||
|
||||
if (parent.GetConfig().data().pin_to_main)
|
||||
OnMainMove(main_position, main_size);
|
||||
|
||||
m_breakpoints->Bind(wxEVT_COMMAND_LIST_ITEM_CHECKED, (wxObjectEventFunction)&BreakpointWindow::OnBreakpointToggled, this);
|
||||
m_breakpoints->Bind(wxEVT_COMMAND_LIST_ITEM_UNCHECKED, (wxObjectEventFunction)&BreakpointWindow::OnBreakpointToggled, this);
|
||||
m_breakpoints->Bind(wxEVT_LEFT_DCLICK, &BreakpointWindow::OnLeftDClick, this);
|
||||
m_breakpoints->Bind(wxEVT_RIGHT_DOWN, &BreakpointWindow::OnRightDown, this);
|
||||
|
||||
OnUpdateView();
|
||||
}
|
||||
|
||||
BreakpointWindow::~BreakpointWindow()
|
||||
{
|
||||
m_breakpoints->Unbind(wxEVT_COMMAND_LIST_ITEM_CHECKED, (wxObjectEventFunction)&BreakpointWindow::OnBreakpointToggled, this);
|
||||
m_breakpoints->Unbind(wxEVT_COMMAND_LIST_ITEM_UNCHECKED, (wxObjectEventFunction)&BreakpointWindow::OnBreakpointToggled, this);
|
||||
}
|
||||
|
||||
void BreakpointWindow::OnMainMove(const wxPoint& main_position, const wxSize& main_size)
|
||||
{
|
||||
wxSize size(420, 250);
|
||||
this->SetSize(size);
|
||||
|
||||
wxPoint position = main_position;
|
||||
position.x -= 420;
|
||||
position.y += main_size.y - 250;
|
||||
this->SetPosition(position);
|
||||
}
|
||||
|
||||
void BreakpointWindow::OnUpdateView()
|
||||
{
|
||||
Freeze();
|
||||
|
||||
m_breakpoints->DeleteAllItems();
|
||||
|
||||
if (!debuggerState.breakpoints.empty())
|
||||
{
|
||||
uint32_t i = 0;
|
||||
for (const auto bpBase : debuggerState.breakpoints)
|
||||
{
|
||||
|
||||
DebuggerBreakpoint* bp = bpBase;
|
||||
while (bp)
|
||||
{
|
||||
wxListItem item;
|
||||
item.SetId(i++);
|
||||
|
||||
const auto index = m_breakpoints->InsertItem(item);
|
||||
m_breakpoints->SetItem(index, ColumnAddress, wxString::Format("%08x", bp->address));
|
||||
const char* typeName = "UKN";
|
||||
if (bp->bpType == DEBUGGER_BP_T_NORMAL)
|
||||
typeName = "X";
|
||||
else if (bp->bpType == DEBUGGER_BP_T_ONE_SHOT)
|
||||
typeName = "XS";
|
||||
else if (bp->bpType == DEBUGGER_BP_T_MEMORY_READ)
|
||||
typeName = "R";
|
||||
else if (bp->bpType == DEBUGGER_BP_T_MEMORY_WRITE)
|
||||
typeName = "W";
|
||||
|
||||
m_breakpoints->SetItem(index, ColumnType, typeName);
|
||||
m_breakpoints->SetItem(index, ColumnComment, bp->comment);
|
||||
m_breakpoints->SetItemPtrData(item, (wxUIntPtr)bp);
|
||||
m_breakpoints->Check(index, bp->enabled);
|
||||
|
||||
bp = bp->next;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Thaw();
|
||||
}
|
||||
|
||||
void BreakpointWindow::OnGameLoaded()
|
||||
{
|
||||
OnUpdateView();
|
||||
}
|
||||
|
||||
void BreakpointWindow::OnBreakpointToggled(wxListEvent& event)
|
||||
{
|
||||
const int32_t index = event.GetIndex();
|
||||
if (0 <= index && index < m_breakpoints->GetItemCount())
|
||||
{
|
||||
const bool state = m_breakpoints->IsChecked(index);
|
||||
wxString line = m_breakpoints->GetItemText(index, ColumnAddress);
|
||||
DebuggerBreakpoint* bp = (DebuggerBreakpoint*)m_breakpoints->GetItemData(index);
|
||||
const uint32 address = std::stoul(line.c_str().AsChar(), nullptr, 16);
|
||||
debugger_toggleBreakpoint(address, state, bp);
|
||||
}
|
||||
}
|
||||
|
||||
void BreakpointWindow::OnLeftDClick(wxMouseEvent& event)
|
||||
{
|
||||
const auto position = event.GetPosition();
|
||||
const sint32 index = (position.y / m_breakpoints->GetCharHeight()) - 2;
|
||||
if (index < 0 || index >= m_breakpoints->GetItemCount())
|
||||
return;
|
||||
|
||||
sint32 x = position.x;
|
||||
const auto enabled_width = m_breakpoints->GetColumnWidth(ColumnEnabled);
|
||||
if (x <= enabled_width)
|
||||
return;
|
||||
|
||||
x -= enabled_width;
|
||||
const auto address_width = m_breakpoints->GetColumnWidth(ColumnAddress);
|
||||
if(x <= address_width)
|
||||
{
|
||||
const auto item = m_breakpoints->GetItemText(index, ColumnAddress);
|
||||
const auto address = std::stoul(item.ToStdString(), nullptr, 16);
|
||||
debuggerState.debugSession.instructionPointer = address;
|
||||
debuggerWindow_moveIP();
|
||||
return;
|
||||
}
|
||||
|
||||
x -= address_width;
|
||||
const auto type_width = m_breakpoints->GetColumnWidth(ColumnType);
|
||||
if (x <= type_width)
|
||||
return;
|
||||
|
||||
x -= type_width;
|
||||
const auto comment_width = m_breakpoints->GetColumnWidth(ColumnComment);
|
||||
if(x <= comment_width)
|
||||
{
|
||||
if (index >= debuggerState.breakpoints.size())
|
||||
return;
|
||||
|
||||
const auto item = m_breakpoints->GetItemText(index, ColumnAddress);
|
||||
const auto address = std::stoul(item.ToStdString(), nullptr, 16);
|
||||
|
||||
auto it = debuggerState.breakpoints.begin();
|
||||
std::advance(it, index);
|
||||
|
||||
wxTextEntryDialog set_value_dialog(this, _("Enter a new comment."), wxString::Format(_("Set comment for breakpoint at address %08x"), address), (*it)->comment);
|
||||
if (set_value_dialog.ShowModal() == wxID_OK)
|
||||
{
|
||||
(*it)->comment = set_value_dialog.GetValue().ToStdWstring();
|
||||
m_breakpoints->SetItem(index, ColumnComment, set_value_dialog.GetValue());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void BreakpointWindow::OnRightDown(wxMouseEvent& event)
|
||||
{
|
||||
wxMenu menu;
|
||||
|
||||
menu.Append(MENU_ID_CREATE_MEM_BP_READ, _("Create memory breakpoint (read)"));
|
||||
menu.Append(MENU_ID_CREATE_MEM_BP_WRITE, _("Create memory breakpoint (write)"));
|
||||
|
||||
menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(BreakpointWindow::OnContextMenuClick), nullptr, this);
|
||||
PopupMenu(&menu);
|
||||
}
|
||||
|
||||
void BreakpointWindow::OnContextMenuClick(wxCommandEvent& evt)
|
||||
{
|
||||
switch (evt.GetId())
|
||||
{
|
||||
case MENU_ID_CREATE_MEM_BP_READ:
|
||||
MemoryBreakpointDialog(false);
|
||||
return;
|
||||
case MENU_ID_CREATE_MEM_BP_WRITE:
|
||||
MemoryBreakpointDialog(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void BreakpointWindow::MemoryBreakpointDialog(bool isWrite)
|
||||
{
|
||||
wxTextEntryDialog goto_dialog(this, _("Enter a memory address"), _("Memory breakpoint"), wxEmptyString);
|
||||
if (goto_dialog.ShowModal() == wxID_OK)
|
||||
{
|
||||
ExpressionParser parser;
|
||||
|
||||
auto value = goto_dialog.GetValue().ToStdString();
|
||||
std::transform(value.begin(), value.end(), value.begin(), tolower);
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
debugger_addParserSymbols(parser);
|
||||
const auto result = (uint32)parser.Evaluate(value);
|
||||
debug_printf("goto eval result: %x\n", result);
|
||||
|
||||
debugger_createMemoryBreakpoint(result, isWrite == false, isWrite == true);
|
||||
this->OnUpdateView();
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
//ctx.errorHandler.printError(nullptr, -1, fmt::format("Unexpected error in expression \"{}\"", expressionString));
|
||||
//return EXPRESSION_RESOLVE_RESULT::EXPRESSION_ERROR;
|
||||
wxMessageBox(e.what(), "Invalid expression");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
26
src/gui/debugger/BreakpointWindow.h
Normal file
26
src/gui/debugger/BreakpointWindow.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
#include "gui/wxcomponents/checkedlistctrl.h"
|
||||
|
||||
class DebuggerWindow2;
|
||||
|
||||
class BreakpointWindow : public wxFrame
|
||||
{
|
||||
public:
|
||||
BreakpointWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size);
|
||||
virtual ~BreakpointWindow();
|
||||
|
||||
void OnMainMove(const wxPoint& position, const wxSize& main_size);
|
||||
void OnUpdateView();
|
||||
void OnGameLoaded();
|
||||
|
||||
private:
|
||||
void OnBreakpointToggled(wxListEvent& event);
|
||||
void OnLeftDClick(wxMouseEvent& event);
|
||||
void OnRightDown(wxMouseEvent& event);
|
||||
|
||||
void OnContextMenuClick(wxCommandEvent& evt);
|
||||
|
||||
void MemoryBreakpointDialog(bool isWrite);
|
||||
|
||||
wxCheckedListCtrl* m_breakpoints;
|
||||
};
|
||||
676
src/gui/debugger/DebuggerWindow2.cpp
Normal file
676
src/gui/debugger/DebuggerWindow2.cpp
Normal file
|
|
@ -0,0 +1,676 @@
|
|||
#include "gui/wxgui.h"
|
||||
#include "gui/debugger/DebuggerWindow2.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include "config/ActiveSettings.h"
|
||||
#include "Cafe/OS/RPL/rpl_structs.h"
|
||||
#include "Cafe/OS/RPL/rpl_debug_symbols.h"
|
||||
|
||||
#include "gui/debugger/RegisterWindow.h"
|
||||
#include "gui/debugger/DumpWindow.h"
|
||||
|
||||
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
|
||||
|
||||
#include "Cafe/OS/RPL/rpl.h"
|
||||
#include "gui/debugger/DisasmCtrl.h"
|
||||
#include "gui/debugger/SymbolWindow.h"
|
||||
#include "gui/debugger/BreakpointWindow.h"
|
||||
#include "gui/debugger/ModuleWindow.h"
|
||||
#include "util/helpers/helpers.h"
|
||||
|
||||
#if BOOST_OS_LINUX
|
||||
#include "resource/linux/resources.h"
|
||||
#endif
|
||||
|
||||
enum
|
||||
{
|
||||
// file
|
||||
MENU_ID_FILE_EXIT = wxID_HIGHEST + 8000,
|
||||
// settings
|
||||
MENU_ID_OPTIONS_PIN_TO_MAINWINDOW,
|
||||
MENU_ID_OPTIONS_BREAK_ON_START,
|
||||
// window
|
||||
MENU_ID_WINDOW_REGISTERS,
|
||||
MENU_ID_WINDOW_DUMP,
|
||||
MENU_ID_WINDOW_STACK,
|
||||
MENU_ID_WINDOW_BREAKPOINTS,
|
||||
MENU_ID_WINDOW_MODULE,
|
||||
MENU_ID_WINDOW_SYMBOL,
|
||||
|
||||
// tool
|
||||
TOOL_ID_GOTO,
|
||||
TOOL_ID_BP,
|
||||
TOOL_ID_PAUSE,
|
||||
TOOL_ID_STEP_OVER,
|
||||
TOOL_ID_STEP_INTO,
|
||||
};
|
||||
|
||||
wxDEFINE_EVENT(wxEVT_DEBUGGER_CLOSE, wxCloseEvent);
|
||||
wxDEFINE_EVENT(wxEVT_UPDATE_VIEW, wxCommandEvent);
|
||||
wxDEFINE_EVENT(wxEVT_BREAKPOINT_CHANGE, wxCommandEvent);
|
||||
wxDEFINE_EVENT(wxEVT_BREAKPOINT_HIT, wxCommandEvent);
|
||||
wxDEFINE_EVENT(wxEVT_MOVE_IP, wxCommandEvent);
|
||||
wxDEFINE_EVENT(wxEVT_RUN, wxCommandEvent);
|
||||
wxDEFINE_EVENT(wxEVT_NOTIFY_MODULE_LOADED, wxCommandEvent);
|
||||
wxDEFINE_EVENT(wxEVT_NOTIFY_MODULE_UNLOADED, wxCommandEvent);
|
||||
|
||||
wxBEGIN_EVENT_TABLE(DebuggerWindow2, wxFrame)
|
||||
EVT_SHOW(DebuggerWindow2::OnShow)
|
||||
EVT_CLOSE(DebuggerWindow2::OnClose)
|
||||
EVT_COMMAND(wxID_ANY, wxEVT_UPDATE_VIEW, DebuggerWindow2::OnUpdateView)
|
||||
EVT_COMMAND(wxID_ANY, wxEVT_BREAKPOINT_CHANGE, DebuggerWindow2::OnBreakpointChange)
|
||||
EVT_COMMAND(wxID_ANY, wxEVT_MOVE_IP, DebuggerWindow2::OnMoveIP)
|
||||
EVT_COMMAND(wxID_ANY, wxEVT_COMMAND_TOOL_CLICKED, DebuggerWindow2::OnToolClicked)
|
||||
EVT_COMMAND(wxID_ANY, wxEVT_BREAKPOINT_HIT, DebuggerWindow2::OnBreakpointHit)
|
||||
EVT_COMMAND(wxID_ANY, wxEVT_RUN, DebuggerWindow2::OnRunProgram)
|
||||
EVT_COMMAND(wxID_ANY, wxEVT_NOTIFY_MODULE_LOADED, DebuggerWindow2::OnNotifyModuleLoaded)
|
||||
EVT_COMMAND(wxID_ANY, wxEVT_NOTIFY_MODULE_UNLOADED, DebuggerWindow2::OnNotifyModuleUnloaded)
|
||||
// file menu
|
||||
EVT_MENU(MENU_ID_FILE_EXIT, DebuggerWindow2::OnExit)
|
||||
// setting
|
||||
EVT_MENU(MENU_ID_OPTIONS_PIN_TO_MAINWINDOW, DebuggerWindow2::OnOptionsInput)
|
||||
// window
|
||||
EVT_MENU_RANGE(MENU_ID_WINDOW_REGISTERS, MENU_ID_WINDOW_MODULE, DebuggerWindow2::OnWindowMenu)
|
||||
wxEND_EVENT_TABLE()
|
||||
|
||||
DebuggerWindow2* g_debugger_window;
|
||||
|
||||
void DebuggerConfig::Load(XMLConfigParser& parser)
|
||||
{
|
||||
pin_to_main = parser.get("PinToMainWindow", true);
|
||||
break_on_start = parser.get("break_on_start", true);
|
||||
|
||||
auto window_parser = parser.get("Windows");
|
||||
show_register = window_parser.get("Registers", true);
|
||||
show_dump = window_parser.get("MemoryDump", true);
|
||||
show_stack = window_parser.get("Stack", true);
|
||||
show_breakpoints = window_parser.get("Breakpoints", true);
|
||||
show_modules = window_parser.get("Modules", true);
|
||||
show_symbols = window_parser.get("Symbols", true);
|
||||
}
|
||||
|
||||
void DebuggerConfig::Save(XMLConfigParser& parser)
|
||||
{
|
||||
parser.set("PinToMainWindow", pin_to_main);
|
||||
parser.set("break_on_start", break_on_start);
|
||||
|
||||
auto window_parser = parser.set("Windows");
|
||||
window_parser.set("Registers", show_register);
|
||||
window_parser.set("MemoryDump", show_dump);
|
||||
window_parser.set("Stack", show_stack);
|
||||
window_parser.set("Breakpoints", show_breakpoints);
|
||||
window_parser.set("Modules", show_modules);
|
||||
window_parser.set("Symbols", show_symbols);
|
||||
}
|
||||
|
||||
void DebuggerModuleStorage::Load(XMLConfigParser& parser)
|
||||
{
|
||||
auto breakpoints_parser = parser.get("Breakpoints");
|
||||
for (auto element = breakpoints_parser.get("Entry"); element.valid(); element = breakpoints_parser.get("Entry", element))
|
||||
{
|
||||
const auto address_string = element.get("Address", "");
|
||||
if (*address_string == '\0')
|
||||
continue;
|
||||
|
||||
uint32 relative_address = std::stoul(address_string, nullptr, 16);
|
||||
if (relative_address == 0)
|
||||
continue;
|
||||
|
||||
auto type = element.get("Type", 0);
|
||||
auto enabled = element.get("Enabled", true);
|
||||
const auto comment = element.get("Comment", "");
|
||||
|
||||
// calculate absolute address
|
||||
uint32 module_base_address = (type == DEBUGGER_BP_T_NORMAL ? this->rpl_module->regionMappingBase_text.GetMPTR() : this->rpl_module->regionMappingBase_data);
|
||||
uint32 address = module_base_address + relative_address;
|
||||
|
||||
// don't change anything if there's already a breakpoint
|
||||
if (debugger_getFirstBP(address) != nullptr)
|
||||
continue;
|
||||
|
||||
// register breakpoints in debugger
|
||||
if (type == DEBUGGER_BP_T_NORMAL)
|
||||
debugger_createExecuteBreakpoint(address);
|
||||
else if (type == DEBUGGER_BP_T_MEMORY_READ)
|
||||
debugger_createMemoryBreakpoint(address, true, false);
|
||||
else if (type == DEBUGGER_BP_T_MEMORY_WRITE)
|
||||
debugger_createMemoryBreakpoint(address, false, true);
|
||||
else
|
||||
continue;
|
||||
|
||||
DebuggerBreakpoint* debugBreakpoint = debugger_getFirstBP(address);
|
||||
|
||||
if (!enabled)
|
||||
debugger_toggleBreakpoint(address, false, debugBreakpoint);
|
||||
|
||||
debugBreakpoint->comment = boost::nowide::widen(comment);
|
||||
}
|
||||
|
||||
auto comments_parser = parser.get("Comments");
|
||||
for (XMLConfigParser element = comments_parser.get("Entry"); element.valid(); element = comments_parser.get("Entry", element))
|
||||
{
|
||||
const auto address_string = element.get("Address", "");
|
||||
if (*address_string == '\0')
|
||||
continue;
|
||||
|
||||
uint32 address = std::stoul(address_string, nullptr, 16);
|
||||
if (address == 0)
|
||||
continue;
|
||||
|
||||
const auto comment = element.get("Comment", "");
|
||||
if (*comment == '\0')
|
||||
continue;
|
||||
|
||||
rplDebugSymbol_createComment(address, boost::nowide::widen(comment).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void DebuggerModuleStorage::Save(XMLConfigParser& parser)
|
||||
{
|
||||
auto breakpoints_parser = parser.set("Breakpoints");
|
||||
for (const auto& bp : debuggerState.breakpoints)
|
||||
{
|
||||
// check breakpoint type
|
||||
if (bp->dbType != DEBUGGER_BP_T_DEBUGGER)
|
||||
continue;
|
||||
|
||||
// check whether the breakpoint is part of the current module being saved
|
||||
RPLModule* address_module;
|
||||
if (bp->bpType == DEBUGGER_BP_T_NORMAL) address_module = RPLLoader_FindModuleByCodeAddr(bp->address);
|
||||
else if (bp->isMemBP()) address_module = RPLLoader_FindModuleByDataAddr(bp->address);
|
||||
else continue;
|
||||
|
||||
if (!address_module || !(address_module->moduleName2 == this->module_name && address_module->patchCRC == this->crc_hash))
|
||||
continue;
|
||||
|
||||
uint32_t relative_address = bp->address - (bp->isMemBP() ? address_module->regionMappingBase_data : address_module->regionMappingBase_text.GetMPTR());
|
||||
auto entry = breakpoints_parser.set("Entry");
|
||||
entry.set("Address", fmt::format("{:#10x}", relative_address));
|
||||
entry.set("Comment", boost::nowide::narrow(bp->comment).c_str());
|
||||
entry.set("Type", bp->bpType);
|
||||
entry.set("Enabled", bp->enabled);
|
||||
|
||||
if (this->delete_breakpoints_after_saving) debugger_deleteBreakpoint(bp);
|
||||
this->delete_breakpoints_after_saving = false;
|
||||
}
|
||||
|
||||
auto comments_parser = parser.set("Comments");
|
||||
for (const auto& comment_entry : rplDebugSymbol_getSymbols())
|
||||
{
|
||||
// check comment type
|
||||
const auto comment_address = comment_entry.first;
|
||||
const auto comment = static_cast<rplDebugSymbolComment*>(comment_entry.second);
|
||||
if (!comment || comment->type != RplDebugSymbolComment)
|
||||
continue;
|
||||
|
||||
// check whether it's part of the current module being saved
|
||||
RPLModule* address_module = RPLLoader_FindModuleByCodeAddr(comment_entry.first);
|
||||
if (!address_module || !(address_module->moduleName2 == module_name && address_module->patchCRC == this->crc_hash))
|
||||
continue;
|
||||
|
||||
uint32_t relative_address = comment_address - address_module->regionMappingBase_text.GetMPTR();
|
||||
auto entry = comments_parser.set("Entry");
|
||||
entry.set("Address", fmt::format("{:#10x}", relative_address));
|
||||
entry.set("Comment", boost::nowide::narrow(comment->comment).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void DebuggerWindow2::CreateToolBar()
|
||||
{
|
||||
m_toolbar = wxFrame::CreateToolBar(wxTB_HORIZONTAL, wxID_ANY);
|
||||
m_toolbar->SetToolBitmapSize(wxSize(1, 1));
|
||||
|
||||
m_toolbar->AddTool(TOOL_ID_GOTO, wxEmptyString, wxBITMAP_PNG(DEBUGGER_GOTO), wxNullBitmap, wxITEM_NORMAL, _("GoTo (CTRL + G)"), "test", NULL);
|
||||
m_toolbar->AddSeparator();
|
||||
|
||||
m_toolbar->AddTool(TOOL_ID_BP, wxEmptyString, wxBITMAP_PNG(DEBUGGER_BP_RED), wxNullBitmap, wxITEM_NORMAL, _("Toggle Breakpoint (F9)"), wxEmptyString, NULL);
|
||||
m_toolbar->AddSeparator();
|
||||
|
||||
m_pause = wxBITMAP_PNG(DEBUGGER_PAUSE);
|
||||
m_run = wxBITMAP_PNG(DEBUGGER_PLAY);
|
||||
m_toolbar->AddTool(TOOL_ID_PAUSE, wxEmptyString, m_pause, wxNullBitmap, wxITEM_NORMAL, _("Break (F5)"), wxEmptyString, NULL);
|
||||
|
||||
m_toolbar->AddTool(TOOL_ID_STEP_INTO, wxEmptyString, wxBITMAP_PNG(DEBUGGER_STEP_INTO), wxNullBitmap, wxITEM_NORMAL, _("Step Into (F11)"), wxEmptyString, NULL);
|
||||
m_toolbar->AddTool(TOOL_ID_STEP_OVER, wxEmptyString, wxBITMAP_PNG(DEBUGGER_STEP_OVER), wxNullBitmap, wxITEM_NORMAL, _("Step Over (F10)"), wxEmptyString, NULL);
|
||||
m_toolbar->AddSeparator();
|
||||
|
||||
m_toolbar->Realize();
|
||||
|
||||
m_toolbar->EnableTool(TOOL_ID_STEP_INTO, false);
|
||||
m_toolbar->EnableTool(TOOL_ID_STEP_OVER, false);
|
||||
}
|
||||
|
||||
void DebuggerWindow2::SaveModuleStorage(const RPLModule* module, bool delete_breakpoints)
|
||||
{
|
||||
auto path = GetModuleStoragePath(module->moduleName2, module->patchCRC);
|
||||
for (auto& module_storage : m_modules_storage)
|
||||
{
|
||||
if (module_storage->data().module_name == module->moduleName2 && module_storage->data().crc_hash == module->patchCRC)
|
||||
{
|
||||
module_storage->data().delete_breakpoints_after_saving = delete_breakpoints;
|
||||
module_storage->Save(path);
|
||||
if (delete_breakpoints) m_modules_storage.erase(std::find(m_modules_storage.begin(), m_modules_storage.end(), module_storage));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DebuggerWindow2::LoadModuleStorage(const RPLModule* module)
|
||||
{
|
||||
auto path = GetModuleStoragePath(module->moduleName2, module->patchCRC);
|
||||
bool already_loaded = std::any_of(m_modules_storage.begin(), m_modules_storage.end(), [path](const std::unique_ptr<XMLDebuggerModuleConfig>& debug) { return debug->GetFilename() == path; });
|
||||
if (!path.empty() && !already_loaded)
|
||||
{
|
||||
m_modules_storage.emplace_back(std::move(new XMLDebuggerModuleConfig(path, { module->moduleName2, module->patchCRC, module, false })));
|
||||
}
|
||||
}
|
||||
|
||||
DebuggerWindow2::DebuggerWindow2(wxFrame& parent, const wxRect& display_size)
|
||||
: wxFrame(&parent, wxID_ANY, wxT("PPC Debugger"), wxDefaultPosition, wxSize(1280, 300), wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLOSE_BOX | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxFRAME_FLOAT_ON_PARENT),
|
||||
m_module_address(0)
|
||||
{
|
||||
this->wxWindowBase::SetBackgroundColour(*wxWHITE);
|
||||
|
||||
const auto file = ActiveSettings::GetPath("debugger/config.xml");
|
||||
m_config.SetFilename(file.generic_wstring());
|
||||
m_config.Load();
|
||||
|
||||
debuggerState.breakOnEntry = m_config.data().break_on_start;
|
||||
|
||||
m_main_position = parent.GetPosition();
|
||||
m_main_size = parent.GetSize();
|
||||
|
||||
auto y = std::max<uint32>(300, (display_size.GetHeight() - 500 - 300) * 0.8);
|
||||
this->SetSize(1280, y);
|
||||
|
||||
CreateMenuBar();
|
||||
CreateToolBar();
|
||||
|
||||
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
// load configs for already loaded modules
|
||||
const auto module_count = RPLLoader_GetModuleCount();
|
||||
const auto module_list = RPLLoader_GetModuleList();
|
||||
for (sint32 i = 0; i < module_count; i++)
|
||||
{
|
||||
const auto module = module_list[i];
|
||||
LoadModuleStorage(module);
|
||||
}
|
||||
|
||||
wxString label_text = _("> no modules loaded");
|
||||
if (module_count != 0)
|
||||
{
|
||||
RPLModule* current_rpl_module = RPLLoader_FindModuleByCodeAddr(MEMORY_CODEAREA_ADDR);
|
||||
if (current_rpl_module)
|
||||
label_text = wxString::Format("> %s", current_rpl_module->moduleName2.c_str());
|
||||
else
|
||||
label_text = _("> unknown module");
|
||||
}
|
||||
|
||||
m_module_label = new wxStaticText(this, wxID_ANY, label_text);
|
||||
m_module_label->SetBackgroundColour(*wxWHITE);
|
||||
m_module_label->SetForegroundColour(wxColour(0xFFbf52fe));
|
||||
main_sizer->Add(m_module_label, 0, wxEXPAND | wxALL, 5);
|
||||
|
||||
m_disasm_ctrl = new DisasmCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxScrolledWindowStyle);
|
||||
main_sizer->Add(m_disasm_ctrl, 1, wxEXPAND);
|
||||
|
||||
this->SetSizer(main_sizer);
|
||||
this->wxWindowBase::Layout();
|
||||
|
||||
m_register_window = new RegisterWindow(*this, m_main_position, m_main_size);
|
||||
m_dump_window = new DumpWindow(*this, m_main_position, m_main_size);
|
||||
m_breakpoint_window = new BreakpointWindow(*this, m_main_position, m_main_size);
|
||||
m_module_window = new ModuleWindow(*this, m_main_position, m_main_size);
|
||||
m_symbol_window = new SymbolWindow(*this, m_main_position, m_main_size);
|
||||
|
||||
const bool value = m_config.data().pin_to_main;
|
||||
m_config.data().pin_to_main = true;
|
||||
OnParentMove(m_main_position, m_main_size);
|
||||
m_config.data().pin_to_main = value;
|
||||
|
||||
g_debugger_window = this;
|
||||
}
|
||||
|
||||
DebuggerWindow2::~DebuggerWindow2()
|
||||
{
|
||||
debuggerState.breakOnEntry = false;
|
||||
g_debugger_window = nullptr;
|
||||
|
||||
// save configs for all modules that are still loaded
|
||||
// doesn't delete breakpoints since that should (in the future) be done by unloading the rpl modules when exiting the current game
|
||||
const auto module_count = RPLLoader_GetModuleCount();
|
||||
const auto module_list = RPLLoader_GetModuleList();
|
||||
for (sint32 i = 0; i < module_count; i++)
|
||||
{
|
||||
const auto module = module_list[i];
|
||||
if (module)
|
||||
SaveModuleStorage(module, false);
|
||||
}
|
||||
|
||||
if (m_register_window && m_register_window->IsShown())
|
||||
m_register_window->Close(true);
|
||||
|
||||
if (m_dump_window && m_dump_window->IsShown())
|
||||
m_dump_window->Close(true);
|
||||
|
||||
if (m_breakpoint_window && m_breakpoint_window->IsShown())
|
||||
m_breakpoint_window->Close(true);
|
||||
|
||||
if (m_module_window && m_module_window->IsShown())
|
||||
m_module_window->Close(true);
|
||||
|
||||
if (m_symbol_window && m_symbol_window->IsShown())
|
||||
m_symbol_window->Close(true);
|
||||
|
||||
m_config.Save();
|
||||
}
|
||||
|
||||
void DebuggerWindow2::OnClose(wxCloseEvent& event)
|
||||
{
|
||||
debuggerState.breakOnEntry = false;
|
||||
|
||||
const wxCloseEvent parentEvent(wxEVT_DEBUGGER_CLOSE);
|
||||
wxPostEvent(m_parent, parentEvent);
|
||||
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void DebuggerWindow2::OnMoveIP(wxCommandEvent& event)
|
||||
{
|
||||
const auto ip = debuggerState.debugSession.instructionPointer;
|
||||
UpdateModuleLabel(ip);
|
||||
m_disasm_ctrl->CenterOffset(ip);
|
||||
}
|
||||
|
||||
void DebuggerWindow2::OnParentMove(const wxPoint& main_position, const wxSize& main_size)
|
||||
{
|
||||
m_main_position = main_position;
|
||||
m_main_size = main_size;
|
||||
|
||||
if (!m_config.data().pin_to_main)
|
||||
return;
|
||||
|
||||
wxSize size(m_main_size.x, GetSize().GetHeight());
|
||||
SetSize(size);
|
||||
|
||||
wxPoint position = m_main_position;
|
||||
position.y -= size.y;
|
||||
SetPosition(position);
|
||||
|
||||
m_register_window->OnMainMove(main_position, main_size);
|
||||
m_dump_window->OnMainMove(main_position, main_size);
|
||||
m_breakpoint_window->OnMainMove(main_position, main_size);
|
||||
m_module_window->OnMainMove(main_position, main_size);
|
||||
m_symbol_window->OnMainMove(main_position, main_size);
|
||||
}
|
||||
|
||||
void DebuggerWindow2::OnNotifyModuleLoaded(wxCommandEvent& event)
|
||||
{
|
||||
RPLModule* module = (RPLModule*)event.GetClientData();
|
||||
LoadModuleStorage(module);
|
||||
m_module_window->OnGameLoaded();
|
||||
m_symbol_window->OnGameLoaded();
|
||||
m_disasm_ctrl->Init();
|
||||
}
|
||||
|
||||
void DebuggerWindow2::OnNotifyModuleUnloaded(wxCommandEvent& event)
|
||||
{
|
||||
RPLModule* module = (RPLModule*)event.GetClientData();
|
||||
SaveModuleStorage(module, true);
|
||||
m_module_window->OnGameLoaded();
|
||||
m_symbol_window->OnGameLoaded();
|
||||
m_disasm_ctrl->Init();
|
||||
}
|
||||
|
||||
void DebuggerWindow2::OnGameLoaded()
|
||||
{
|
||||
m_disasm_ctrl->Init();
|
||||
|
||||
m_dump_window->OnGameLoaded();
|
||||
m_module_window->OnGameLoaded();
|
||||
m_breakpoint_window->OnGameLoaded();
|
||||
m_symbol_window->OnGameLoaded();
|
||||
|
||||
RPLModule* current_rpl_module = RPLLoader_FindModuleByCodeAddr(MEMORY_CODEAREA_ADDR);
|
||||
if(current_rpl_module)
|
||||
m_module_label->SetLabel(wxString::Format("> %s", current_rpl_module->moduleName2.c_str()));
|
||||
|
||||
this->SendSizeEvent();
|
||||
}
|
||||
|
||||
XMLDebuggerConfig& DebuggerWindow2::GetConfig()
|
||||
{
|
||||
return m_config;
|
||||
}
|
||||
|
||||
bool DebuggerWindow2::Show(bool show)
|
||||
{
|
||||
const bool result = wxFrame::Show(show);
|
||||
|
||||
if (show)
|
||||
{
|
||||
m_register_window->Show(m_config.data().show_register);
|
||||
m_dump_window->Show(m_config.data().show_dump);
|
||||
m_breakpoint_window->Show(m_config.data().show_breakpoints);
|
||||
m_module_window->Show(m_config.data().show_modules);
|
||||
m_symbol_window->Show(m_config.data().show_symbols);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_register_window->Show(false);
|
||||
m_dump_window->Show(false);
|
||||
m_breakpoint_window->Show(false);
|
||||
m_module_window->Show(false);
|
||||
m_symbol_window->Show(false);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::wstring DebuggerWindow2::GetModuleStoragePath(std::string module_name, uint32_t crc_hash) const
|
||||
{
|
||||
if (module_name.empty() || crc_hash == 0) return std::wstring();
|
||||
return ActiveSettings::GetPath("debugger/{}_{:#10x}.xml", module_name, crc_hash).generic_wstring();
|
||||
}
|
||||
|
||||
void DebuggerWindow2::OnBreakpointHit(wxCommandEvent& event)
|
||||
{
|
||||
const auto ip = debuggerState.debugSession.instructionPointer;
|
||||
UpdateModuleLabel(ip);
|
||||
|
||||
m_toolbar->SetToolShortHelp(TOOL_ID_PAUSE, _("Run (F5)"));
|
||||
m_toolbar->SetToolNormalBitmap(TOOL_ID_PAUSE, m_run);
|
||||
|
||||
m_toolbar->EnableTool(TOOL_ID_STEP_INTO, true);
|
||||
m_toolbar->EnableTool(TOOL_ID_STEP_OVER, true);
|
||||
|
||||
m_disasm_ctrl->CenterOffset(ip);
|
||||
}
|
||||
|
||||
void DebuggerWindow2::OnRunProgram(wxCommandEvent& event)
|
||||
{
|
||||
m_toolbar->SetToolShortHelp(TOOL_ID_PAUSE, _("Break (F5)"));
|
||||
m_toolbar->SetToolNormalBitmap(TOOL_ID_PAUSE, m_pause);
|
||||
|
||||
m_toolbar->EnableTool(TOOL_ID_STEP_INTO, false);
|
||||
m_toolbar->EnableTool(TOOL_ID_STEP_OVER, false);
|
||||
}
|
||||
|
||||
void DebuggerWindow2::OnToolClicked(wxCommandEvent& event)
|
||||
{
|
||||
switch(event.GetId())
|
||||
{
|
||||
case TOOL_ID_GOTO:
|
||||
m_disasm_ctrl->GoToAddressDialog();
|
||||
break;
|
||||
case TOOL_ID_PAUSE:
|
||||
if (debugger_isTrapped())
|
||||
debugger_resume();
|
||||
else
|
||||
debugger_forceBreak();
|
||||
break;
|
||||
case TOOL_ID_STEP_INTO:
|
||||
if (debugger_isTrapped())
|
||||
debuggerState.debugSession.stepInto = true;
|
||||
break;
|
||||
case TOOL_ID_STEP_OVER:
|
||||
if (debugger_isTrapped())
|
||||
debuggerState.debugSession.stepOver = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DebuggerWindow2::OnBreakpointChange(wxCommandEvent& event)
|
||||
{
|
||||
m_breakpoint_window->OnUpdateView();
|
||||
UpdateModuleLabel();
|
||||
}
|
||||
|
||||
void DebuggerWindow2::OnOptionsInput(wxCommandEvent& event)
|
||||
{
|
||||
switch(event.GetId())
|
||||
{
|
||||
case MENU_ID_OPTIONS_PIN_TO_MAINWINDOW:
|
||||
{
|
||||
const bool value = !m_config.data().pin_to_main;
|
||||
m_config.data().pin_to_main = value;
|
||||
if(value)
|
||||
OnParentMove(m_main_position, m_main_size);
|
||||
|
||||
break;
|
||||
}
|
||||
case MENU_ID_OPTIONS_BREAK_ON_START:
|
||||
{
|
||||
const bool value = !m_config.data().break_on_start;
|
||||
m_config.data().break_on_start = value;
|
||||
debuggerState.breakOnEntry = value;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
m_config.Save();
|
||||
}
|
||||
|
||||
void DebuggerWindow2::OnWindowMenu(wxCommandEvent& event)
|
||||
{
|
||||
switch (event.GetId())
|
||||
{
|
||||
case MENU_ID_WINDOW_REGISTERS:
|
||||
{
|
||||
const bool value = !m_config.data().show_register;
|
||||
m_config.data().show_register = value;
|
||||
m_register_window->Show(value);
|
||||
break;
|
||||
}
|
||||
case MENU_ID_WINDOW_DUMP:
|
||||
{
|
||||
const bool value = !m_config.data().show_dump;
|
||||
m_config.data().show_dump = value;
|
||||
m_dump_window->Show(value);
|
||||
break;
|
||||
}
|
||||
case MENU_ID_WINDOW_BREAKPOINTS:
|
||||
{
|
||||
const bool value = !m_config.data().show_breakpoints;
|
||||
m_config.data().show_breakpoints = value;
|
||||
m_breakpoint_window->Show(value);
|
||||
break;
|
||||
}
|
||||
case MENU_ID_WINDOW_MODULE:
|
||||
{
|
||||
const bool value = !m_config.data().show_modules;
|
||||
m_config.data().show_modules = value;
|
||||
m_module_window->Show(value);
|
||||
break;
|
||||
}
|
||||
case MENU_ID_WINDOW_SYMBOL:
|
||||
{
|
||||
const bool value = !m_config.data().show_symbols;
|
||||
m_config.data().show_symbols = value;
|
||||
m_symbol_window->Show(value);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
m_config.Save();
|
||||
}
|
||||
|
||||
void DebuggerWindow2::OnUpdateView(wxCommandEvent& event)
|
||||
{
|
||||
UpdateModuleLabel();
|
||||
m_disasm_ctrl->OnUpdateView();
|
||||
m_register_window->OnUpdateView();
|
||||
m_breakpoint_window->OnUpdateView();
|
||||
}
|
||||
|
||||
void DebuggerWindow2::OnExit(wxCommandEvent& event)
|
||||
{
|
||||
this->Close();
|
||||
}
|
||||
|
||||
void DebuggerWindow2::OnShow(wxShowEvent& event)
|
||||
{
|
||||
m_register_window->Show();
|
||||
m_dump_window->Show();
|
||||
m_breakpoint_window->Show();
|
||||
m_module_window->Show();
|
||||
m_symbol_window->Show();
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void DebuggerWindow2::CreateMenuBar()
|
||||
{
|
||||
auto menu_bar = new wxMenuBar;
|
||||
|
||||
// file
|
||||
wxMenu* file_menu = new wxMenu;
|
||||
file_menu->Append(MENU_ID_FILE_EXIT, _("&Exit"));
|
||||
file_menu->Bind(wxEVT_MENU, &DebuggerWindow2::OnExit, this);
|
||||
|
||||
menu_bar->Append(file_menu, _("&File"));
|
||||
|
||||
// options
|
||||
wxMenu* options_menu = new wxMenu;
|
||||
options_menu->Append(MENU_ID_OPTIONS_PIN_TO_MAINWINDOW, _("&Pin to main window"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().pin_to_main);
|
||||
options_menu->Append(MENU_ID_OPTIONS_BREAK_ON_START, _("Break on &entry point"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().break_on_start);
|
||||
menu_bar->Append(options_menu, _("&Options"));
|
||||
|
||||
// window
|
||||
wxMenu* window_menu = new wxMenu;
|
||||
window_menu->Append(MENU_ID_WINDOW_REGISTERS, _("&Registers"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().show_register);
|
||||
window_menu->Append(MENU_ID_WINDOW_DUMP, _("&Memory Dump"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().show_dump);
|
||||
window_menu->Append(MENU_ID_WINDOW_BREAKPOINTS, _("&Breakpoints"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().show_breakpoints);
|
||||
window_menu->Append(MENU_ID_WINDOW_MODULE, _("Module&list"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().show_modules);
|
||||
window_menu->Append(MENU_ID_WINDOW_SYMBOL, _("&Symbols"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().show_symbols);
|
||||
|
||||
menu_bar->Append(window_menu, _("&Window"));
|
||||
|
||||
SetMenuBar(menu_bar);
|
||||
|
||||
options_menu->Bind(wxEVT_MENU, &DebuggerWindow2::OnOptionsInput, this);
|
||||
window_menu->Bind(wxEVT_MENU, &DebuggerWindow2::OnWindowMenu, this);
|
||||
}
|
||||
|
||||
void DebuggerWindow2::UpdateModuleLabel(uint32 address)
|
||||
{
|
||||
if(address == 0)
|
||||
address = m_disasm_ctrl->GetViewBaseAddress();
|
||||
|
||||
RPLModule* module = RPLLoader_FindModuleByCodeAddr(address);
|
||||
if (module && m_module_address != module->regionMappingBase_text.GetMPTR())
|
||||
{
|
||||
m_module_label->SetLabel(wxString::Format("> %s", module->moduleName2.c_str()));
|
||||
m_module_address = module->regionMappingBase_text.GetMPTR();
|
||||
}
|
||||
else if (address >= mmuRange_CODECAVE.getBase() && address < mmuRange_CODECAVE.getEnd())
|
||||
{
|
||||
m_module_label->SetLabel(wxString::Format("> %s", "Cemu codecave"));
|
||||
m_module_address = mmuRange_CODECAVE.getBase();
|
||||
}
|
||||
}
|
||||
116
src/gui/debugger/DebuggerWindow2.h
Normal file
116
src/gui/debugger/DebuggerWindow2.h
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
#pragma once
|
||||
|
||||
#include "gui/debugger/DisasmCtrl.h"
|
||||
#include "config/XMLConfig.h"
|
||||
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
|
||||
#include "Cafe/OS/RPL/rpl.h"
|
||||
|
||||
#include <wx/bitmap.h>
|
||||
#include <wx/frame.h>
|
||||
|
||||
class BreakpointWindow;
|
||||
class RegisterWindow;
|
||||
class DumpWindow;
|
||||
class ModuleWindow;
|
||||
class SymbolWindow;
|
||||
class wxStaticText;
|
||||
|
||||
wxDECLARE_EVENT(wxEVT_UPDATE_VIEW, wxCommandEvent);
|
||||
wxDECLARE_EVENT(wxEVT_BREAKPOINT_HIT, wxCommandEvent);
|
||||
wxDECLARE_EVENT(wxEVT_RUN, wxCommandEvent);
|
||||
wxDECLARE_EVENT(wxEVT_BREAKPOINT_CHANGE, wxCommandEvent);
|
||||
wxDECLARE_EVENT(wxEVT_MOVE_IP, wxCommandEvent);
|
||||
wxDECLARE_EVENT(wxEVT_NOTIFY_MODULE_LOADED, wxCommandEvent);
|
||||
wxDECLARE_EVENT(wxEVT_NOTIFY_MODULE_UNLOADED, wxCommandEvent);
|
||||
|
||||
struct DebuggerConfig
|
||||
{
|
||||
DebuggerConfig()
|
||||
: pin_to_main(true), break_on_start(true), show_register(true), show_dump(true), show_stack(true), show_breakpoints(true), show_modules(true) {}
|
||||
|
||||
bool pin_to_main;
|
||||
|
||||
bool break_on_start;
|
||||
|
||||
bool show_register;
|
||||
bool show_dump;
|
||||
bool show_stack;
|
||||
bool show_breakpoints;
|
||||
bool show_modules;
|
||||
bool show_symbols;
|
||||
|
||||
void Load(XMLConfigParser& parser);
|
||||
void Save(XMLConfigParser& parser);
|
||||
};
|
||||
typedef XMLDataConfig<DebuggerConfig, &DebuggerConfig::Load, &DebuggerConfig::Save> XMLDebuggerConfig;
|
||||
|
||||
struct DebuggerModuleStorage
|
||||
{
|
||||
std::string module_name;
|
||||
uint32_t crc_hash;
|
||||
const RPLModule* rpl_module;
|
||||
bool delete_breakpoints_after_saving;
|
||||
|
||||
void Load(XMLConfigParser& parser);
|
||||
void Save(XMLConfigParser& parser);
|
||||
};
|
||||
typedef XMLDataConfig<DebuggerModuleStorage, &DebuggerModuleStorage::Load, &DebuggerModuleStorage::Save> XMLDebuggerModuleConfig;
|
||||
|
||||
class DebuggerWindow2 : public wxFrame
|
||||
{
|
||||
public:
|
||||
void CreateToolBar();
|
||||
void LoadModuleStorage(const RPLModule* module);
|
||||
void SaveModuleStorage(const RPLModule* module, bool delete_breakpoints);
|
||||
DebuggerWindow2(wxFrame& parent, const wxRect& display_size);
|
||||
~DebuggerWindow2();
|
||||
|
||||
void OnParentMove(const wxPoint& position, const wxSize& size);
|
||||
void OnGameLoaded();
|
||||
|
||||
XMLDebuggerConfig& GetConfig();
|
||||
|
||||
bool Show(bool show = true) override;
|
||||
std::wstring GetModuleStoragePath(std::string module_name, uint32_t crc_hash) const;
|
||||
private:
|
||||
void OnBreakpointHit(wxCommandEvent& event);
|
||||
void OnRunProgram(wxCommandEvent& event);
|
||||
void OnToolClicked(wxCommandEvent& event);
|
||||
void OnBreakpointChange(wxCommandEvent& event);
|
||||
void OnOptionsInput(wxCommandEvent& event);
|
||||
void OnWindowMenu(wxCommandEvent& event);
|
||||
void OnUpdateView(wxCommandEvent& event);
|
||||
void OnExit(wxCommandEvent& event);
|
||||
void OnShow(wxShowEvent& event);
|
||||
void OnClose(wxCloseEvent& event);
|
||||
void OnMoveIP(wxCommandEvent& event);
|
||||
void OnNotifyModuleLoaded(wxCommandEvent& event);
|
||||
void OnNotifyModuleUnloaded(wxCommandEvent& event);
|
||||
|
||||
void CreateMenuBar();
|
||||
void UpdateModuleLabel(uint32 address = 0);
|
||||
|
||||
XMLDebuggerConfig m_config;
|
||||
std::vector<std::unique_ptr<XMLDebuggerModuleConfig>> m_modules_storage;
|
||||
|
||||
wxPoint m_main_position;
|
||||
wxSize m_main_size;
|
||||
|
||||
RegisterWindow* m_register_window;
|
||||
DumpWindow* m_dump_window;
|
||||
BreakpointWindow* m_breakpoint_window;
|
||||
ModuleWindow* m_module_window;
|
||||
SymbolWindow* m_symbol_window;
|
||||
|
||||
DisasmCtrl* m_disasm_ctrl;
|
||||
|
||||
wxToolBar* m_toolbar;
|
||||
wxBitmap m_run, m_pause;
|
||||
|
||||
uint32 m_module_address;
|
||||
wxStaticText* m_module_label;
|
||||
|
||||
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
806
src/gui/debugger/DisasmCtrl.cpp
Normal file
806
src/gui/debugger/DisasmCtrl.cpp
Normal file
|
|
@ -0,0 +1,806 @@
|
|||
#include "gui/wxgui.h"
|
||||
#include "gui/debugger/DisasmCtrl.h"
|
||||
|
||||
#include "Cafe/OS/RPL/rpl_structs.h"
|
||||
#include "Cafe/OS/RPL/rpl.h"
|
||||
#include "Cafe/OS/RPL/rpl_symbol_storage.h"
|
||||
#include "Cafe/OS/RPL/rpl_debug_symbols.h"
|
||||
#include "Cemu/PPCAssembler/ppcAssembler.h"
|
||||
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
|
||||
#include "gui/debugger/DebuggerWindow2.h"
|
||||
#include "util/helpers/helpers.h"
|
||||
#include "gui/guiWrapper.h"
|
||||
|
||||
#include "Cemu/ExpressionParser/ExpressionParser.h"
|
||||
#include "Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h"
|
||||
#include <wx/mstream.h> // for wxMemoryInputStream
|
||||
|
||||
#define MAX_SYMBOL_LEN (120)
|
||||
|
||||
#define COLOR_DEBUG_ACTIVE_BP 0xFFFFA0FF
|
||||
#define COLOR_DEBUG_ACTIVE 0xFFFFA080
|
||||
#define COLOR_DEBUG_BP 0xFF8080FF
|
||||
|
||||
#define SYNTAX_COLOR_GPR 0xFF000066
|
||||
#define SYNTAX_COLOR_FPR 0xFF006666
|
||||
#define SYNTAX_COLOR_SPR 0xFF666600
|
||||
#define SYNTAX_COLOR_CR 0xFF666600
|
||||
#define SYNTAX_COLOR_IMM 0xFF006600
|
||||
#define SYNTAX_COLOR_IMM_OFFSET 0xFF006600
|
||||
#define SYNTAX_COLOR_CIMM 0xFF880000
|
||||
#define SYNTAX_COLOR_PSEUDO 0xFFA0A0A0 // color for pseudo code
|
||||
#define SYNTAX_COLOR_SYMBOL 0xFF0000A0 // color for function symbol
|
||||
|
||||
#define OFFSET_ADDRESS (60)
|
||||
#define OFFSET_ADDRESS_RELATIVE (90)
|
||||
#define OFFSET_DISASSEMBLY (300)
|
||||
|
||||
#define OFFSET_DISASSEMBLY_OPERAND (80)
|
||||
|
||||
wxBitmap* g_ipArrowBitmap = nullptr;
|
||||
|
||||
uint8 _arrowRightPNG[] =
|
||||
{
|
||||
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D,
|
||||
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0B,
|
||||
0x08, 0x03, 0x00, 0x00, 0x00, 0x41, 0x3C, 0xFD, 0x0B, 0x00, 0x00, 0x00,
|
||||
0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00,
|
||||
0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC,
|
||||
0x61, 0x05, 0x00, 0x00, 0x00, 0x06, 0x50, 0x4C, 0x54, 0x45, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xA5, 0x67, 0xB9, 0xCF, 0x00, 0x00, 0x00, 0x02,
|
||||
0x74, 0x52, 0x4E, 0x53, 0xFF, 0x00, 0xE5, 0xB7, 0x30, 0x4A, 0x00, 0x00,
|
||||
0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0E, 0xC3, 0x00, 0x00,
|
||||
0x0E, 0xC3, 0x01, 0xC7, 0x6F, 0xA8, 0x64, 0x00, 0x00, 0x00, 0x2C, 0x49,
|
||||
0x44, 0x41, 0x54, 0x18, 0x57, 0x63, 0x60, 0x84, 0x03, 0x08, 0x13, 0x59,
|
||||
0x00, 0xCC, 0x46, 0x11, 0x00, 0x71, 0x80, 0x24, 0x32, 0xC0, 0x10, 0x60,
|
||||
0xC0, 0x10, 0xC0, 0x00, 0x58, 0xCC, 0x80, 0xD8, 0x00, 0x02, 0x60, 0x3E,
|
||||
0x7E, 0x77, 0x00, 0x31, 0x23, 0x23, 0x00, 0x21, 0x95, 0x00, 0x5B, 0x20,
|
||||
0x73, 0x8D, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE,
|
||||
0x42, 0x60, 0x82
|
||||
};
|
||||
|
||||
DisasmCtrl::DisasmCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size, long style)
|
||||
: TextList(parent, id, pos, size, style), m_mouse_line(-1), m_mouse_line_drawn(-1), m_active_line(-1)
|
||||
{
|
||||
Init();
|
||||
|
||||
if (!g_ipArrowBitmap)
|
||||
{
|
||||
wxMemoryInputStream strm(_arrowRightPNG, sizeof(_arrowRightPNG));
|
||||
wxImage img(strm, wxBITMAP_TYPE_PNG);
|
||||
g_ipArrowBitmap = new wxBitmap(img);
|
||||
}
|
||||
|
||||
auto tooltip_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
tooltip_sizer->Add(new wxStaticText(m_tooltip_window, wxID_ANY, wxEmptyString), 0, wxALL, 5);
|
||||
m_tooltip_window->SetSizer(tooltip_sizer);
|
||||
}
|
||||
|
||||
void DisasmCtrl::Init()
|
||||
{
|
||||
SelectCodeRegion(mmuRange_TEXT_AREA.getBase());
|
||||
}
|
||||
|
||||
void DisasmCtrl::SelectCodeRegion(uint32 newAddress)
|
||||
{
|
||||
if (newAddress >= mmuRange_TEXT_AREA.getBase() && newAddress < mmuRange_TEXT_AREA.getEnd())
|
||||
{
|
||||
currentCodeRegionStart = MEMORY_CODEAREA_ADDR;
|
||||
currentCodeRegionEnd = RPLLoader_GetMaxCodeOffset();
|
||||
currentCodeRegionEnd = std::max(currentCodeRegionEnd, currentCodeRegionStart + 0x1000);
|
||||
}
|
||||
MMURange* mmuRange = memory_getMMURangeByAddress(newAddress);
|
||||
if (mmuRange)
|
||||
{
|
||||
currentCodeRegionStart = mmuRange->getBase();
|
||||
currentCodeRegionEnd = mmuRange->getEnd();
|
||||
}
|
||||
else
|
||||
{
|
||||
currentCodeRegionStart = 0;
|
||||
currentCodeRegionEnd = 0;
|
||||
}
|
||||
|
||||
// update line tracking
|
||||
sint32 element_count = currentCodeRegionEnd - currentCodeRegionStart;
|
||||
if (element_count <= 0x00010000)
|
||||
element_count = 0x00010000;
|
||||
|
||||
if (this->SetElementCount(element_count / 4))
|
||||
{
|
||||
Scroll(0, 0);
|
||||
RefreshControl();
|
||||
}
|
||||
}
|
||||
|
||||
void DisasmCtrl::DrawDisassemblyLine(wxDC& dc, const wxPoint& linePosition, MPTR virtualAddress, RPLModule* rplModule)
|
||||
{
|
||||
wxPoint position = linePosition;
|
||||
|
||||
bool hasPatch = debugger_hasPatch(virtualAddress);
|
||||
|
||||
PPCDisassembledInstruction disasmInstr;
|
||||
|
||||
const DebuggerBreakpoint* bp = debugger_getFirstBP(virtualAddress);
|
||||
while (bp)
|
||||
{
|
||||
if (bp->isExecuteBP() && bp->enabled)
|
||||
break;
|
||||
bp = bp->next;
|
||||
}
|
||||
|
||||
uint32 opcode;
|
||||
|
||||
if (bp)
|
||||
opcode = bp->originalOpcodeValue;
|
||||
else
|
||||
opcode = memory_readU32(virtualAddress);
|
||||
|
||||
ppcAssembler_disassemble(virtualAddress, opcode, &disasmInstr);
|
||||
|
||||
const bool is_active_bp = debuggerState.debugSession.isTrapped && debuggerState.debugSession.instructionPointer == virtualAddress;
|
||||
|
||||
// write virtual address
|
||||
wxColour background_colour;
|
||||
if (is_active_bp && bp != nullptr)
|
||||
background_colour = wxColour(0xFFFFA0FF);
|
||||
else if (is_active_bp)
|
||||
background_colour = wxColour(0xFF80A0FF);
|
||||
else if (bp != nullptr)
|
||||
background_colour = wxColour(0xFF8080FF);
|
||||
else if(virtualAddress == m_lastGotoTarget)
|
||||
background_colour = wxColour(0xFFE0E0E0);
|
||||
else
|
||||
background_colour = wxColour(COLOR_WHITE);
|
||||
|
||||
DrawLineBackground(dc, position, background_colour);
|
||||
|
||||
dc.SetTextForeground(COLOR_BLACK);
|
||||
dc.DrawText(wxString::Format("%08x", virtualAddress), position);
|
||||
position.x += OFFSET_ADDRESS;
|
||||
|
||||
dc.SetTextForeground(COLOR_GREY);
|
||||
if (rplModule)
|
||||
dc.DrawText(wxString::Format("+0x%-8x", virtualAddress - rplModule->regionMappingBase_text.GetMPTR()), position);
|
||||
else
|
||||
dc.DrawText("???", position);
|
||||
|
||||
position.x += OFFSET_ADDRESS_RELATIVE;
|
||||
|
||||
// draw arrow to clearly indicate instruction pointer
|
||||
if(is_active_bp)
|
||||
dc.DrawBitmap(*g_ipArrowBitmap, wxPoint(position.x - 24, position.y + 2), false);
|
||||
|
||||
// handle data symbols
|
||||
auto debugSymbolDataType = DebugSymbolStorage::GetDataType(virtualAddress);
|
||||
if (debugSymbolDataType == DEBUG_SYMBOL_TYPE::FLOAT)
|
||||
{
|
||||
dc.SetTextForeground(hasPatch ? wxColour(0xFF2020FF) : wxColour(0xFF400000));
|
||||
dc.DrawText(fmt::format(".float"), position);
|
||||
|
||||
position.x += OFFSET_DISASSEMBLY_OPERAND;
|
||||
dc.SetTextForeground(hasPatch ? wxColour(0xFF2020FF) : wxColour(SYNTAX_COLOR_IMM));
|
||||
dc.DrawText(fmt::format("{}", memory_readFloat(virtualAddress)), position);
|
||||
|
||||
return;
|
||||
}
|
||||
else if (debugSymbolDataType == DEBUG_SYMBOL_TYPE::U32)
|
||||
{
|
||||
dc.SetTextForeground(hasPatch ? wxColour(0xFF2020FF) : wxColour(0xFF400000));
|
||||
dc.DrawText(fmt::format(".uint"), position);
|
||||
|
||||
position.x += OFFSET_DISASSEMBLY_OPERAND;
|
||||
dc.SetTextForeground(hasPatch ? wxColour(0xFF2020FF) : wxColour(SYNTAX_COLOR_IMM));
|
||||
dc.DrawText(fmt::format("{}", memory_readU32(virtualAddress)), position);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sint32 start_width = position.x;
|
||||
dc.SetTextForeground(hasPatch ? wxColour(0xFF2020FF) : wxColour(0xFF400000));
|
||||
char opName[32];
|
||||
strcpy(opName, ppcAssembler_getInstructionName(disasmInstr.ppcAsmCode));
|
||||
std::transform(opName, opName + sizeof(opName), opName, tolower);
|
||||
dc.DrawText(wxString::Format("%-12s", opName), position);
|
||||
position.x += OFFSET_DISASSEMBLY_OPERAND;
|
||||
|
||||
bool isRLWINM = disasmInstr.ppcAsmCode == PPCASM_OP_RLWINM || disasmInstr.ppcAsmCode == PPCASM_OP_RLWINM_ ||
|
||||
disasmInstr.ppcAsmCode == PPCASM_OP_CLRLWI || disasmInstr.ppcAsmCode == PPCASM_OP_CLRLWI_ ||
|
||||
disasmInstr.ppcAsmCode == PPCASM_OP_CLRRWI || disasmInstr.ppcAsmCode == PPCASM_OP_CLRRWI_ ||
|
||||
disasmInstr.ppcAsmCode == PPCASM_OP_EXTLWI || disasmInstr.ppcAsmCode == PPCASM_OP_EXTLWI_ ||
|
||||
disasmInstr.ppcAsmCode == PPCASM_OP_EXTRWI || disasmInstr.ppcAsmCode == PPCASM_OP_EXTRWI_ ||
|
||||
disasmInstr.ppcAsmCode == PPCASM_OP_SLWI || disasmInstr.ppcAsmCode == PPCASM_OP_SLWI_ ||
|
||||
disasmInstr.ppcAsmCode == PPCASM_OP_SRWI || disasmInstr.ppcAsmCode == PPCASM_OP_SRWI_ ||
|
||||
disasmInstr.ppcAsmCode == PPCASM_OP_ROTRWI || disasmInstr.ppcAsmCode == PPCASM_OP_ROTRWI_ ||
|
||||
disasmInstr.ppcAsmCode == PPCASM_OP_ROTLWI || disasmInstr.ppcAsmCode == PPCASM_OP_ROTLWI_;
|
||||
bool forceDecDisplay = isRLWINM;
|
||||
|
||||
if (disasmInstr.ppcAsmCode == PPCASM_OP_UKN)
|
||||
{
|
||||
// show raw bytes
|
||||
WriteText(dc, wxString::Format("%02x %02x %02x %02x", (opcode >> 24) & 0xFF, (opcode >> 16) & 0xFF, (opcode >> 8) & 0xFF, (opcode >> 0) & 0xFF), position, SYNTAX_COLOR_PSEUDO);
|
||||
}
|
||||
|
||||
bool is_first_operand = true;
|
||||
for (sint32 o = 0; o < PPCASM_OPERAND_COUNT; o++)
|
||||
{
|
||||
if (((disasmInstr.operandMask >> o) & 1) == 0)
|
||||
continue;
|
||||
|
||||
if (!is_first_operand)
|
||||
WriteText(dc, ", ", position, COLOR_BLACK);
|
||||
|
||||
is_first_operand = false;
|
||||
switch (disasmInstr.operand[o].type)
|
||||
{
|
||||
case PPCASM_OPERAND_TYPE_GPR:
|
||||
WriteText(dc, wxString::Format("r%d", disasmInstr.operand[o].registerIndex), position, SYNTAX_COLOR_GPR);
|
||||
break;
|
||||
|
||||
case PPCASM_OPERAND_TYPE_FPR:
|
||||
WriteText(dc, wxString::Format("f%d", disasmInstr.operand[o].registerIndex), position, SYNTAX_COLOR_FPR);
|
||||
break;
|
||||
|
||||
case PPCASM_OPERAND_TYPE_SPR:
|
||||
WriteText(dc, wxString::Format("spr%d", disasmInstr.operand[o].registerIndex), position, SYNTAX_COLOR_SPR);
|
||||
break;
|
||||
|
||||
case PPCASM_OPERAND_TYPE_CR:
|
||||
WriteText(dc, wxString::Format("cr%d", disasmInstr.operand[o].registerIndex), position, SYNTAX_COLOR_CR);
|
||||
break;
|
||||
|
||||
case PPCASM_OPERAND_TYPE_IMM:
|
||||
{
|
||||
wxString string;
|
||||
if (disasmInstr.operand[o].isSignedImm)
|
||||
{
|
||||
sint32 sImm = disasmInstr.operand[o].immS32;
|
||||
if (disasmInstr.operand[o].immWidth == 16 && (sImm & 0x8000))
|
||||
sImm |= 0xFFFF0000;
|
||||
|
||||
if ((sImm > -10 && sImm < 10) || forceDecDisplay)
|
||||
string = wxString::Format("%d", sImm);
|
||||
else
|
||||
{
|
||||
if (sImm < 0)
|
||||
string = wxString::Format("-0x%x", -sImm);
|
||||
else
|
||||
string = wxString::Format("0x%x", sImm);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32 uImm = disasmInstr.operand[o].immS32;
|
||||
if ((uImm >= 0 && uImm < 10) || forceDecDisplay)
|
||||
string = wxString::Format("%u", uImm);
|
||||
else
|
||||
string = wxString::Format("0x%x", uImm);
|
||||
}
|
||||
|
||||
|
||||
WriteText(dc, string, position, SYNTAX_COLOR_IMM);
|
||||
break;
|
||||
}
|
||||
case PPCASM_OPERAND_TYPE_PSQMODE:
|
||||
{
|
||||
if (disasmInstr.operand[o].immS32)
|
||||
WriteText(dc, "single", position, SYNTAX_COLOR_IMM);
|
||||
else
|
||||
WriteText(dc, "paired", position, SYNTAX_COLOR_IMM);
|
||||
break;
|
||||
}
|
||||
case PPCASM_OPERAND_TYPE_CIMM:
|
||||
{
|
||||
wxString string;
|
||||
// use symbol for function calls if available
|
||||
uint32 callDest = disasmInstr.operand[o].immU32;
|
||||
RPLStoredSymbol* storedSymbol = nullptr;
|
||||
if (disasmInstr.ppcAsmCode == PPCASM_OP_BL || disasmInstr.ppcAsmCode == PPCASM_OP_BLA)
|
||||
storedSymbol = rplSymbolStorage_getByAddress(callDest);
|
||||
|
||||
if (storedSymbol)
|
||||
{
|
||||
// if symbol is within same module then don't display libname prefix
|
||||
RPLModule* rplModuleCurrent = RPLLoader_FindModuleByCodeAddr(virtualAddress); // cache this
|
||||
if (rplModuleCurrent && callDest >= rplModuleCurrent->regionMappingBase_text.GetMPTR() && callDest < (rplModuleCurrent->regionMappingBase_text.GetMPTR() + rplModuleCurrent->regionSize_text))
|
||||
string = wxString((char*)storedSymbol->symbolName);
|
||||
else
|
||||
string = wxString::Format("%s.%s", (char*)storedSymbol->libName, (char*)storedSymbol->symbolName);
|
||||
|
||||
// truncate name after 36 characters
|
||||
if (string.Length() >= 36)
|
||||
{
|
||||
string.Truncate(34);
|
||||
string.Append("..");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string = wxString::Format("0x%08x", disasmInstr.operand[o].immU32);
|
||||
}
|
||||
|
||||
WriteText(dc, string, position, SYNTAX_COLOR_CIMM);
|
||||
|
||||
if (disasmInstr.ppcAsmCode != PPCASM_OP_BL)
|
||||
{
|
||||
wxString x = wxString(" ");
|
||||
if (callDest <= virtualAddress)
|
||||
x.Append(wxUniChar(0x2191)); // arrow up
|
||||
else
|
||||
x.Append(wxUniChar(0x2193)); // arrow down
|
||||
|
||||
WriteText(dc, x, position, COLOR_BLACK);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case PPCASM_OPERAND_TYPE_MEM:
|
||||
{
|
||||
// offset
|
||||
wxString string;
|
||||
if (disasmInstr.operand[o].isSignedImm && disasmInstr.operand[o].immS32 >= 0)
|
||||
string = wxString::Format("+0x%x", disasmInstr.operand[o].immS32);
|
||||
else
|
||||
string = wxString::Format("-0x%x", -disasmInstr.operand[o].immS32);
|
||||
|
||||
WriteText(dc, string, position, SYNTAX_COLOR_IMM_OFFSET);
|
||||
WriteText(dc, "(", position, COLOR_BLACK);
|
||||
|
||||
// register
|
||||
WriteText(dc, wxString::Format("r%d", disasmInstr.operand[o].registerIndex), position, SYNTAX_COLOR_GPR);
|
||||
WriteText(dc, ")", position, COLOR_BLACK);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// TODO
|
||||
WriteText(dc, "<TODO>", position, wxColour(0xFF444444));
|
||||
}
|
||||
}
|
||||
|
||||
position.x = start_width + OFFSET_DISASSEMBLY;
|
||||
const auto comment = static_cast<rplDebugSymbolComment*>(rplDebugSymbol_getForAddress(virtualAddress));
|
||||
if (comment && comment->type == RplDebugSymbolComment)
|
||||
WriteText(dc, comment->comment, position, COLOR_BLACK);
|
||||
else if (isRLWINM)
|
||||
{
|
||||
sint32 rS, rA, SH, MB, ME;
|
||||
rS = (opcode >> 21) & 0x1f;
|
||||
rA = (opcode >> 16) & 0x1f;
|
||||
SH = (opcode >> 11) & 0x1f;
|
||||
MB = (opcode >> 6) & 0x1f;
|
||||
ME = (opcode >> 1) & 0x1f;
|
||||
|
||||
uint32 mask = ppcAssembler_generateMaskRLW(MB, ME);
|
||||
|
||||
wxString string;
|
||||
if (SH == 0)
|
||||
string = wxString::Format("r%d=r%d&0x%x", rA, rS, mask);
|
||||
else if ((0xFFFFFFFF << (uint32)disasmInstr.operand[2].immS32) == mask)
|
||||
string = wxString::Format("r%d=r%d<<%d", rA, rS, SH);
|
||||
else if ((0xFFFFFFFF >> (32 - SH) == mask))
|
||||
string = wxString::Format("r%d=r%d>>%d", rA, rS, 32 - SH);
|
||||
else
|
||||
string = wxString::Format("r%d=(r%d<<<%d)&0x%x", rA, rS, SH, mask);
|
||||
WriteText(dc, string, position, COLOR_GREY);
|
||||
}
|
||||
else if (disasmInstr.ppcAsmCode == PPCASM_OP_SUBF || disasmInstr.ppcAsmCode == PPCASM_OP_SUBF_)
|
||||
{
|
||||
sint32 rD, rA, rB;
|
||||
rD = (opcode >> 21) & 0x1f;
|
||||
rA = (opcode >> 16) & 0x1f;
|
||||
rB = (opcode >> 11) & 0x1f;
|
||||
|
||||
wxString string;
|
||||
string = wxString::Format("r%d=r%d-r%d", rD, rB, rA);
|
||||
WriteText(dc, string, position, COLOR_GREY);
|
||||
}
|
||||
}
|
||||
|
||||
void DisasmCtrl::DrawLabelName(wxDC& dc, const wxPoint& linePosition, MPTR virtualAddress, RPLStoredSymbol* storedSymbol)
|
||||
{
|
||||
wxString symbol_string = wxString::Format("%s:", (char*)storedSymbol->symbolName);
|
||||
if (symbol_string.Length() > MAX_SYMBOL_LEN)
|
||||
{
|
||||
symbol_string.Truncate(MAX_SYMBOL_LEN - 3);
|
||||
symbol_string.Append("..:");
|
||||
}
|
||||
wxPoint tmpPos(linePosition);
|
||||
WriteText(dc, symbol_string, tmpPos, SYNTAX_COLOR_SYMBOL);
|
||||
}
|
||||
|
||||
void DisasmCtrl::OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position)
|
||||
{
|
||||
wxPoint position(0, 0);
|
||||
|
||||
RPLModule* current_rpl_module = RPLLoader_FindModuleByCodeAddr(GetViewBaseAddress());
|
||||
|
||||
if (currentCodeRegionStart == currentCodeRegionEnd)
|
||||
return;
|
||||
|
||||
sint32 viewFirstLine = GetViewStart().y;
|
||||
sint32 lineOffset = start - viewFirstLine;
|
||||
|
||||
cemu_assert_debug(lineOffset >= 0);
|
||||
|
||||
sint32 instructionIndex = 0;
|
||||
sint32 numLinesToUpdate = lineOffset + count;
|
||||
numLinesToUpdate = std::min(numLinesToUpdate, (sint32)m_elements_visible);
|
||||
|
||||
if(m_lineToAddress.size() != m_elements_visible)
|
||||
m_lineToAddress.resize(m_elements_visible);
|
||||
|
||||
sint32 lineIndex = 0;
|
||||
while(lineIndex < numLinesToUpdate)
|
||||
{
|
||||
const uint32 virtualAddress = GetViewBaseAddress() + instructionIndex * 4;
|
||||
instructionIndex++;
|
||||
|
||||
if (virtualAddress < currentCodeRegionStart ||
|
||||
virtualAddress >= currentCodeRegionEnd)
|
||||
{
|
||||
NextLine(position, &start_position);
|
||||
m_lineToAddress[lineIndex] = std::nullopt;
|
||||
lineIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if valid memory address
|
||||
if (!memory_isAddressRangeAccessible(virtualAddress, 4))
|
||||
{
|
||||
NextLine(position, &start_position);
|
||||
m_lineToAddress[lineIndex] = std::nullopt;
|
||||
lineIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// draw symbol as label
|
||||
RPLStoredSymbol* storedSymbol = rplSymbolStorage_getByAddress(virtualAddress);
|
||||
if (storedSymbol)
|
||||
{
|
||||
if(lineIndex >= lineOffset)
|
||||
DrawLabelName(dc, position, virtualAddress, storedSymbol);
|
||||
m_lineToAddress[lineIndex] = virtualAddress;
|
||||
lineIndex++;
|
||||
if (lineIndex >= numLinesToUpdate)
|
||||
break;
|
||||
NextLine(position, &start_position);
|
||||
}
|
||||
m_lineToAddress[lineIndex] = virtualAddress;
|
||||
if (lineIndex >= lineOffset)
|
||||
DrawDisassemblyLine(dc, position, virtualAddress, current_rpl_module);
|
||||
NextLine(position, &start_position);
|
||||
lineIndex++;
|
||||
}
|
||||
|
||||
// draw vertical separator lines: offset | disassembly | comment
|
||||
dc.SetPen(*wxLIGHT_GREY_PEN);
|
||||
|
||||
wxPoint line_from = start_position;
|
||||
line_from.x = OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE - 5;
|
||||
wxPoint line_to = line_from;
|
||||
line_to.y += (count + 1) * m_line_height;
|
||||
dc.DrawLine(line_from, line_to);
|
||||
|
||||
line_from.x += OFFSET_DISASSEMBLY;
|
||||
line_to.x += OFFSET_DISASSEMBLY;
|
||||
dc.DrawLine(line_from, line_to);
|
||||
}
|
||||
|
||||
void DisasmCtrl::OnMouseMove(const wxPoint& start_position, uint32 line)
|
||||
{
|
||||
/*m_mouse_line = line;
|
||||
if (m_mouse_line_drawn != -1)
|
||||
RefreshLine(m_mouse_line_drawn);
|
||||
if (m_mouse_line != -1)
|
||||
RefreshLine(m_mouse_line);*/
|
||||
|
||||
wxPoint position = start_position;
|
||||
|
||||
// address
|
||||
if (position.x <= OFFSET_ADDRESS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
position.x -= OFFSET_ADDRESS;
|
||||
|
||||
// relative offset
|
||||
if (position.x <= OFFSET_ADDRESS_RELATIVE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
position.x -= OFFSET_ADDRESS_RELATIVE;
|
||||
|
||||
// disassembly code
|
||||
if (position.x <= OFFSET_DISASSEMBLY)
|
||||
{
|
||||
if(m_mouse_down)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
if (position.x <= OFFSET_DISASSEMBLY_OPERAND)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
position.x -= OFFSET_DISASSEMBLY_OPERAND;
|
||||
}
|
||||
}
|
||||
|
||||
void DisasmCtrl::OnKeyPressed(sint32 key_code, const wxPoint& position)
|
||||
{
|
||||
auto optVirtualAddress = LinePixelPosToAddress(position.y);
|
||||
switch (key_code)
|
||||
{
|
||||
case WXK_F9:
|
||||
{
|
||||
if (optVirtualAddress)
|
||||
{
|
||||
debugger_toggleExecuteBreakpoint(*optVirtualAddress);
|
||||
|
||||
RefreshControl();
|
||||
|
||||
wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE);
|
||||
wxPostEvent(this->m_parent, evt);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'G':
|
||||
{
|
||||
if(IsKeyDown(WXK_CONTROL))
|
||||
{
|
||||
GoToAddressDialog();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// debugger currently in break state
|
||||
if (debuggerState.debugSession.isTrapped)
|
||||
{
|
||||
switch (key_code)
|
||||
{
|
||||
case WXK_F5:
|
||||
{
|
||||
debuggerState.debugSession.run = true;
|
||||
return;
|
||||
}
|
||||
case WXK_F10:
|
||||
{
|
||||
debuggerState.debugSession.stepOver = true;
|
||||
return;
|
||||
}
|
||||
case WXK_F11:
|
||||
{
|
||||
debuggerState.debugSession.stepInto = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (key_code)
|
||||
{
|
||||
case WXK_F5:
|
||||
{
|
||||
debuggerState.debugSession.shouldBreak = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DisasmCtrl::OnMouseDClick(const wxPoint& position, uint32 line)
|
||||
{
|
||||
wxPoint pos = position;
|
||||
auto optVirtualAddress = LinePixelPosToAddress(position.y - GetViewStart().y * m_line_height);
|
||||
if (!optVirtualAddress)
|
||||
return;
|
||||
MPTR virtualAddress = *optVirtualAddress;
|
||||
|
||||
// address
|
||||
if (pos.x <= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE)
|
||||
{
|
||||
// address + address relative
|
||||
debugger_toggleExecuteBreakpoint(virtualAddress);
|
||||
RefreshLine(line);
|
||||
wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE);
|
||||
wxPostEvent(this->m_parent, evt);
|
||||
return;
|
||||
}
|
||||
else if (pos.x <= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE + OFFSET_DISASSEMBLY)
|
||||
{
|
||||
// double-clicked on disassembly (operation and operand data)
|
||||
wxString currentInstruction = wxEmptyString;
|
||||
wxTextEntryDialog set_value_dialog(this, _("Enter a new instruction."), _(wxString::Format("Overwrite instruction at address %08x", virtualAddress)), currentInstruction);
|
||||
if (set_value_dialog.ShowModal() == wxID_OK)
|
||||
{
|
||||
PPCAssemblerInOut ctx = { 0 };
|
||||
ctx.virtualAddress = virtualAddress;
|
||||
if (ppcAssembler_assembleSingleInstruction(set_value_dialog.GetValue().c_str(), &ctx))
|
||||
{
|
||||
debugger_createPatch(virtualAddress, { ctx.outputData.data(), ctx.outputData.size() });
|
||||
RefreshLine(line);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// comment
|
||||
const auto comment = static_cast<rplDebugSymbolComment*>(rplDebugSymbol_getForAddress(virtualAddress));
|
||||
|
||||
wxString old_comment = wxEmptyString;
|
||||
if (comment && comment->type == RplDebugSymbolComment)
|
||||
old_comment = comment->comment;
|
||||
|
||||
wxTextEntryDialog set_value_dialog(this, _("Enter a new comment."), _(wxString::Format("Create comment at address %08x", virtualAddress)), old_comment);
|
||||
if (set_value_dialog.ShowModal() == wxID_OK)
|
||||
{
|
||||
rplDebugSymbol_createComment(virtualAddress, set_value_dialog.GetValue().wc_str());
|
||||
RefreshLine(line);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void DisasmCtrl::CopyToClipboard(std::string text) {
|
||||
#if BOOST_OS_WINDOWS
|
||||
if (OpenClipboard(nullptr))
|
||||
{
|
||||
EmptyClipboard();
|
||||
const HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, text.size() + 1);
|
||||
if (hGlobal)
|
||||
{
|
||||
memcpy(GlobalLock(hGlobal), text.c_str(), text.size() + 1);
|
||||
GlobalUnlock(hGlobal);
|
||||
|
||||
SetClipboardData(CF_TEXT, hGlobal);
|
||||
GlobalFree(hGlobal);
|
||||
}
|
||||
CloseClipboard();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void DisasmCtrl::OnContextMenu(const wxPoint& position, uint32 line)
|
||||
{
|
||||
wxPoint pos = position;
|
||||
auto optVirtualAddress = LinePixelPosToAddress(position.y - GetViewStart().y * m_line_height);
|
||||
if (!optVirtualAddress)
|
||||
return;
|
||||
MPTR virtualAddress = *optVirtualAddress;
|
||||
|
||||
// address
|
||||
if (pos.x <= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE)
|
||||
{
|
||||
CopyToClipboard(fmt::format("{:#10x}", virtualAddress));
|
||||
return;
|
||||
}
|
||||
else if (pos.x <= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE + OFFSET_DISASSEMBLY)
|
||||
{
|
||||
// double-clicked on disassembly (operation and operand data)
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// comment
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool DisasmCtrl::OnShowTooltip(const wxPoint& position, uint32 line)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void DisasmCtrl::ScrollWindow(int dx, int dy, const wxRect* prect)
|
||||
{
|
||||
// scroll and repaint everything
|
||||
RefreshControl(nullptr);
|
||||
TextList::ScrollWindow(dx, dy, nullptr);
|
||||
}
|
||||
|
||||
wxSize DisasmCtrl::DoGetBestSize() const
|
||||
{
|
||||
return TextList::DoGetBestSize();
|
||||
}
|
||||
|
||||
void DisasmCtrl::OnUpdateView()
|
||||
{
|
||||
RefreshControl();
|
||||
}
|
||||
|
||||
uint32 DisasmCtrl::GetViewBaseAddress() const
|
||||
{
|
||||
if (GetViewStart().y < 0)
|
||||
return MPTR_NULL;
|
||||
return currentCodeRegionStart + GetViewStart().y * 4;
|
||||
}
|
||||
|
||||
std::optional<MPTR> DisasmCtrl::LinePixelPosToAddress(sint32 posY)
|
||||
{
|
||||
if (posY < 0)
|
||||
return std::nullopt;
|
||||
|
||||
|
||||
sint32 lineIndex = posY / m_line_height;
|
||||
if (lineIndex >= m_lineToAddress.size())
|
||||
return std::nullopt;
|
||||
|
||||
return m_lineToAddress[lineIndex];
|
||||
}
|
||||
|
||||
uint32 DisasmCtrl::AddressToScrollPos(uint32 offset) const
|
||||
{
|
||||
return (offset - currentCodeRegionStart) / 4;
|
||||
}
|
||||
|
||||
void DisasmCtrl::CenterOffset(uint32 offset)
|
||||
{
|
||||
if (offset < currentCodeRegionStart || offset >= currentCodeRegionEnd)
|
||||
SelectCodeRegion(offset);
|
||||
|
||||
const sint32 line = AddressToScrollPos(offset);
|
||||
if (line < 0 || line >= (sint32)m_element_count)
|
||||
return;
|
||||
|
||||
if (m_active_line != -1)
|
||||
RefreshLine(m_active_line);
|
||||
|
||||
DoScroll(0, std::max(0, line - (sint32)(m_elements_visible / 2)));
|
||||
|
||||
m_active_line = line;
|
||||
RefreshLine(m_active_line);
|
||||
|
||||
debug_printf("scroll to %x\n", debuggerState.debugSession.instructionPointer);
|
||||
}
|
||||
|
||||
void DisasmCtrl::GoToAddressDialog()
|
||||
{
|
||||
wxTextEntryDialog goto_dialog(this, _("Enter a target address."), _("GoTo address"), wxEmptyString);
|
||||
if (goto_dialog.ShowModal() == wxID_OK)
|
||||
{
|
||||
ExpressionParser parser;
|
||||
|
||||
auto value = goto_dialog.GetValue().ToStdString();
|
||||
std::transform(value.begin(), value.end(), value.begin(), tolower);
|
||||
|
||||
const auto module_count = RPLLoader_GetModuleCount();
|
||||
const auto module_list = RPLLoader_GetModuleList();
|
||||
|
||||
std::vector<double> module_tmp(module_count);
|
||||
for (int i = 0; i < module_count; i++)
|
||||
{
|
||||
const auto module = module_list[i];
|
||||
if (module)
|
||||
{
|
||||
module_tmp[i] = (double)module->regionMappingBase_text.GetMPTR();
|
||||
parser.AddConstant(module->moduleName2, module_tmp[i]);
|
||||
}
|
||||
}
|
||||
|
||||
double grp_tmp[32];
|
||||
PPCSnapshot& ppc_snapshot = debuggerState.debugSession.ppcSnapshot;
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
char var_name[32];
|
||||
sprintf(var_name, "r%d", i);
|
||||
grp_tmp[i] = ppc_snapshot.gpr[i];
|
||||
parser.AddConstant(var_name, grp_tmp[i]);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
const auto result = (uint32)parser.Evaluate(value);
|
||||
debug_printf("goto eval result: %x\n", result);
|
||||
m_lastGotoTarget = result;
|
||||
CenterOffset(result);
|
||||
debuggerWindow_updateViewThreadsafe2();
|
||||
}
|
||||
catch (const std::exception& )
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
48
src/gui/debugger/DisasmCtrl.h
Normal file
48
src/gui/debugger/DisasmCtrl.h
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
#include "gui/components/TextList.h"
|
||||
|
||||
class DisasmCtrl : public TextList
|
||||
{
|
||||
public:
|
||||
DisasmCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size, long style);
|
||||
|
||||
void Init();
|
||||
wxSize DoGetBestSize() const override;
|
||||
|
||||
void OnUpdateView();
|
||||
|
||||
uint32 GetViewBaseAddress() const;
|
||||
std::optional<MPTR> LinePixelPosToAddress(sint32 posY);
|
||||
|
||||
uint32 AddressToScrollPos(uint32 offset) const;
|
||||
void CenterOffset(uint32 offset);
|
||||
void GoToAddressDialog();
|
||||
|
||||
|
||||
|
||||
protected:
|
||||
void OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position) override;
|
||||
void OnMouseMove(const wxPoint& position, uint32 line) override;
|
||||
void OnKeyPressed(sint32 key_code, const wxPoint& position) override;
|
||||
void OnMouseDClick(const wxPoint& position, uint32 line) override;
|
||||
void OnContextMenu(const wxPoint& position, uint32 line) override;
|
||||
bool OnShowTooltip(const wxPoint& position, uint32 line) override;
|
||||
void ScrollWindow(int dx, int dy, const wxRect* prect) override;
|
||||
|
||||
void DrawDisassemblyLine(wxDC& dc, const wxPoint& linePosition, MPTR virtualAddress, struct RPLModule* rplModule);
|
||||
void DrawLabelName(wxDC& dc, const wxPoint& linePosition, MPTR virtualAddress, struct RPLStoredSymbol* storedSymbol);
|
||||
|
||||
void SelectCodeRegion(uint32 newAddress);
|
||||
|
||||
private:
|
||||
void CopyToClipboard(std::string text);
|
||||
|
||||
sint32 m_mouse_line, m_mouse_line_drawn;
|
||||
sint32 m_active_line;
|
||||
uint32 m_lastGotoTarget{};
|
||||
// code region info
|
||||
uint32 currentCodeRegionStart;
|
||||
uint32 currentCodeRegionEnd;
|
||||
// line to address mapping
|
||||
std::vector<std::optional<MPTR>> m_lineToAddress;
|
||||
};
|
||||
316
src/gui/debugger/DumpCtrl.cpp
Normal file
316
src/gui/debugger/DumpCtrl.cpp
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
#include "gui/wxgui.h"
|
||||
#include "gui/debugger/DumpCtrl.h"
|
||||
#include "Cafe/OS/RPL/rpl.h"
|
||||
#include "Cafe/OS/RPL/rpl_structs.h"
|
||||
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
|
||||
#include "util/helpers/helpers.h"
|
||||
|
||||
#include "Cemu/ExpressionParser/ExpressionParser.h"
|
||||
|
||||
#define COLOR_BLACK 0xFF000000
|
||||
#define COLOR_GREY 0xFFA0A0A0
|
||||
#define COLOR_WHITE 0xFFFFFFFF
|
||||
|
||||
#define COLOR_DEBUG_ACTIVE_BP 0xFFFFA0FF
|
||||
#define COLOR_DEBUG_ACTIVE 0xFFFFA080
|
||||
#define COLOR_DEBUG_BP 0xFF8080FF
|
||||
|
||||
#define SYNTAX_COLOR_GPR 0xFF000066
|
||||
#define SYNTAX_COLOR_FPR 0xFF006666
|
||||
#define SYNTAX_COLOR_SPR 0xFF666600
|
||||
#define SYNTAX_COLOR_CR 0xFF666600
|
||||
#define SYNTAX_COLOR_IMM 0xFF006600
|
||||
#define SYNTAX_COLOR_IMM_OFFSET 0xFF006600
|
||||
#define SYNTAX_COLOR_CIMM 0xFF880000
|
||||
#define SYNTAX_COLOR_PSEUDO 0xFFA0A0A0 // color for pseudo code
|
||||
#define SYNTAX_COLOR_SYMBOL 0xFF0000A0 // color for function symbol
|
||||
|
||||
#define OFFSET_ADDRESS (60)
|
||||
#define OFFSET_ADDRESS_RELATIVE (90)
|
||||
#define OFFSET_MEMORY (450)
|
||||
|
||||
#define OFFSET_DISASSEMBLY_OPERAND (80)
|
||||
|
||||
|
||||
DumpCtrl::DumpCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size, long style)
|
||||
: TextList(parent, id, pos, size, style)
|
||||
{
|
||||
MMURange* range = memory_getMMURangeByAddress(0x10000000);
|
||||
if (range)
|
||||
{
|
||||
m_memoryRegion.baseAddress = range->getBase();
|
||||
m_memoryRegion.size = range->getSize();
|
||||
Init();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_memoryRegion.baseAddress = 0x10000000;
|
||||
m_memoryRegion.size = 0x1000;
|
||||
Init();
|
||||
}
|
||||
}
|
||||
|
||||
void DumpCtrl::Init()
|
||||
{
|
||||
uint32 element_count = m_memoryRegion.size;
|
||||
this->SetElementCount(element_count / 0x10);
|
||||
Scroll(0, 0);
|
||||
RefreshControl();
|
||||
}
|
||||
|
||||
void DumpCtrl::OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position)
|
||||
{
|
||||
wxPoint position = start_position;
|
||||
uint32 endAddr = m_memoryRegion.baseAddress + m_memoryRegion.size;
|
||||
RPLModule* currentCodeRPL = RPLLoader_FindModuleByCodeAddr(m_memoryRegion.baseAddress + start);
|
||||
RPLModule* currentDataRPL = RPLLoader_FindModuleByDataAddr(m_memoryRegion.baseAddress + start);
|
||||
for (sint32 i = 0; i <= count; i++)
|
||||
{
|
||||
const uint32 virtual_address = m_memoryRegion.baseAddress + (start + i) * 0x10;
|
||||
|
||||
dc.SetTextForeground(wxColour(COLOR_BLACK));
|
||||
dc.DrawText(wxString::Format("%08x", virtual_address), position);
|
||||
position.x += OFFSET_ADDRESS;
|
||||
|
||||
dc.SetTextForeground(wxColour(COLOR_GREY));
|
||||
if (currentCodeRPL)
|
||||
{
|
||||
dc.DrawText(wxString::Format("+0x%-8x", virtual_address - currentCodeRPL->regionMappingBase_text.GetMPTR()), position);
|
||||
}
|
||||
else if (currentDataRPL)
|
||||
{
|
||||
dc.DrawText(wxString::Format("+0x%-8x", virtual_address - currentDataRPL->regionMappingBase_data), position);
|
||||
}
|
||||
else
|
||||
{
|
||||
dc.DrawText("???", position);
|
||||
}
|
||||
|
||||
position.x += OFFSET_ADDRESS_RELATIVE;
|
||||
|
||||
sint32 start_width = position.x;
|
||||
|
||||
if (!memory_isAddressRangeAccessible(virtual_address, 0x10))
|
||||
{
|
||||
for (sint32 f=0; f<0x10; f++)
|
||||
{
|
||||
wxPoint p(position);
|
||||
WriteText(dc, wxString::Format("?? "), p);
|
||||
position.x += (m_char_width * 3);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::array<uint8, 0x10> data;
|
||||
memory_readBytes(virtual_address, data);
|
||||
for (auto b : data)
|
||||
{
|
||||
wxPoint p(position);
|
||||
WriteText(dc, wxString::Format("%02x ", b), p);
|
||||
position.x += (m_char_width * 3);
|
||||
}
|
||||
position.x = start_width = OFFSET_MEMORY;
|
||||
dc.SetTextForeground(wxColour(COLOR_BLACK));
|
||||
for (auto b : data)
|
||||
{
|
||||
if (isprint(b))
|
||||
dc.DrawText(wxString::Format("%c ", b), position);
|
||||
else
|
||||
dc.DrawText(".", position);
|
||||
|
||||
position.x += m_char_width;
|
||||
}
|
||||
}
|
||||
|
||||
// display goto indicator
|
||||
if (m_lastGotoOffset >= virtual_address && m_lastGotoOffset < (virtual_address + 16))
|
||||
{
|
||||
sint32 indicatorX = start_position.x + OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE + (m_lastGotoOffset - virtual_address) * m_char_width * 3;
|
||||
wxPoint line1(start_position.x + indicatorX - 2, position.y);
|
||||
wxPoint line2(line1.x, line1.y + m_line_height);
|
||||
dc.SetPen(*wxRED_PEN);
|
||||
dc.DrawLine(line1, line2);
|
||||
dc.DrawLine(line1, wxPoint(line1.x + 3, line1.y));
|
||||
dc.DrawLine(line2, wxPoint(line2.x + 3, line2.y));
|
||||
}
|
||||
|
||||
NextLine(position, &start_position);
|
||||
}
|
||||
|
||||
// draw vertical separator lines for 4 byte blocks
|
||||
sint32 cursorOffsetHexBytes = start_position.x + OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE;
|
||||
wxPoint line_from(
|
||||
cursorOffsetHexBytes + m_char_width * (3 * 4 - 1) + m_char_width / 2,
|
||||
start_position.y
|
||||
);
|
||||
wxPoint line_to(line_from.x, line_from.y + m_line_height * (count + 1));
|
||||
dc.SetPen(*wxLIGHT_GREY_PEN);
|
||||
for (sint32 i = 0; i < 3; i++)
|
||||
{
|
||||
dc.DrawLine(line_from, line_to);
|
||||
line_from.x += m_char_width * (3 * 4);
|
||||
line_to.x += m_char_width * (3 * 4);
|
||||
}
|
||||
}
|
||||
|
||||
void DumpCtrl::OnMouseMove(const wxPoint& start_position, uint32 line)
|
||||
{
|
||||
wxPoint position = start_position;
|
||||
|
||||
// address
|
||||
if (position.x <= OFFSET_ADDRESS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
position.x -= OFFSET_ADDRESS;
|
||||
|
||||
// relative offset
|
||||
if (position.x <= OFFSET_ADDRESS_RELATIVE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
position.x -= OFFSET_ADDRESS_RELATIVE;
|
||||
|
||||
// byte code
|
||||
if (position.x <= OFFSET_MEMORY)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// string view
|
||||
position.x -= OFFSET_MEMORY;
|
||||
}
|
||||
|
||||
void DumpCtrl::OnMouseDClick(const wxPoint& position, uint32 line)
|
||||
{
|
||||
wxPoint pos = position;
|
||||
if (pos.x <= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE)
|
||||
return;
|
||||
|
||||
pos.x -= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE;
|
||||
if(pos.x <= OFFSET_MEMORY)
|
||||
{
|
||||
const uint32 byte_index = (pos.x / m_char_width) / 3;
|
||||
const uint32 offset = LineToOffset(line) + byte_index;
|
||||
const uint8 value = memory_readU8(offset);
|
||||
|
||||
wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), _(wxString::Format("Set byte at address %08x", offset)), wxString::Format("%02x", value));
|
||||
if (set_value_dialog.ShowModal() == wxID_OK)
|
||||
{
|
||||
const uint8 new_value = std::stoul(set_value_dialog.GetValue().ToStdString(), nullptr, 16);
|
||||
memory_writeU8(offset, new_value);
|
||||
wxRect update_rect(0, line * m_line_height, GetSize().x, m_line_height);
|
||||
RefreshControl(&update_rect);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void DumpCtrl::GoToAddressDialog()
|
||||
{
|
||||
wxTextEntryDialog goto_dialog(this, _("Enter a target address."), _("GoTo address"), wxEmptyString);
|
||||
if (goto_dialog.ShowModal() == wxID_OK)
|
||||
{
|
||||
try
|
||||
{
|
||||
ExpressionParser parser;
|
||||
|
||||
auto value = goto_dialog.GetValue().ToStdString();
|
||||
std::transform(value.begin(), value.end(), value.begin(), tolower);
|
||||
//parser.SetExpr(value);
|
||||
|
||||
const auto module_count = RPLLoader_GetModuleCount();
|
||||
const auto module_list = RPLLoader_GetModuleList();
|
||||
|
||||
std::vector<double> module_tmp(module_count);
|
||||
for (int i = 0; i < module_count; i++)
|
||||
{
|
||||
const auto module = module_list[i];
|
||||
if (module)
|
||||
{
|
||||
module_tmp[i] = (double)module->regionMappingBase_text.GetMPTR();
|
||||
parser.AddConstant(module->moduleName2, module_tmp[i]);
|
||||
}
|
||||
}
|
||||
|
||||
double grp_tmp[32];
|
||||
PPCSnapshot& ppc_snapshot = debuggerState.debugSession.ppcSnapshot;
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
char var_name[32];
|
||||
sprintf(var_name, "r%d", i);
|
||||
grp_tmp[i] = ppc_snapshot.gpr[i];
|
||||
parser.AddConstant(var_name, grp_tmp[i]);
|
||||
}
|
||||
|
||||
const auto result = (uint32)parser.Evaluate(value);
|
||||
debug_printf("goto eval result: %x\n", result);
|
||||
m_lastGotoOffset = result;
|
||||
CenterOffset(result);
|
||||
}
|
||||
catch (const std::exception ex)
|
||||
{
|
||||
wxMessageBox(ex.what(), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DumpCtrl::CenterOffset(uint32 offset)
|
||||
{
|
||||
// check if the offset is valid
|
||||
if (!memory_isAddressRangeAccessible(offset, 1))
|
||||
return;
|
||||
// set region and line
|
||||
MMURange* range = memory_getMMURangeByAddress(offset);
|
||||
if (m_memoryRegion.baseAddress != range->getBase() || m_memoryRegion.size != range->getSize())
|
||||
{
|
||||
m_memoryRegion.baseAddress = range->getBase();
|
||||
m_memoryRegion.size = range->getSize();
|
||||
Init();
|
||||
}
|
||||
|
||||
const sint32 line = OffsetToLine(offset);
|
||||
if (line < 0 || line >= (sint32)m_element_count)
|
||||
return;
|
||||
|
||||
DoScroll(0, std::max(0, line - ((sint32)m_elements_visible / 2)));
|
||||
|
||||
RefreshControl();
|
||||
//RefreshLine(line);
|
||||
|
||||
debug_printf("scroll to %x\n", debuggerState.debugSession.instructionPointer);
|
||||
}
|
||||
|
||||
uint32 DumpCtrl::LineToOffset(uint32 line)
|
||||
{
|
||||
return m_memoryRegion.baseAddress + line * 0x10;
|
||||
}
|
||||
|
||||
uint32 DumpCtrl::OffsetToLine(uint32 offset)
|
||||
{
|
||||
return (offset - m_memoryRegion.baseAddress) / 0x10;
|
||||
}
|
||||
|
||||
|
||||
void DumpCtrl::OnKeyPressed(sint32 key_code, const wxPoint& position)
|
||||
{
|
||||
switch (key_code)
|
||||
{
|
||||
case 'G':
|
||||
{
|
||||
if (IsKeyDown(WXK_CONTROL))
|
||||
{
|
||||
GoToAddressDialog();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wxSize DumpCtrl::DoGetBestSize() const
|
||||
{
|
||||
return TextList::DoGetBestSize();
|
||||
}
|
||||
30
src/gui/debugger/DumpCtrl.h
Normal file
30
src/gui/debugger/DumpCtrl.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
#include "gui/components/TextList.h"
|
||||
|
||||
|
||||
class DumpCtrl : public TextList
|
||||
{
|
||||
public:
|
||||
DumpCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size, long style);
|
||||
|
||||
void Init();
|
||||
wxSize DoGetBestSize() const override;
|
||||
|
||||
protected:
|
||||
void GoToAddressDialog();
|
||||
void CenterOffset(uint32 offset);
|
||||
uint32 LineToOffset(uint32 line);
|
||||
uint32 OffsetToLine(uint32 offset);
|
||||
|
||||
void OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position) override;
|
||||
void OnMouseMove(const wxPoint& position, uint32 line) override;
|
||||
void OnMouseDClick(const wxPoint& position, uint32 line) override;
|
||||
void OnKeyPressed(sint32 key_code, const wxPoint& position) override;
|
||||
private:
|
||||
struct
|
||||
{
|
||||
uint32 baseAddress;
|
||||
uint32 size;
|
||||
}m_memoryRegion;
|
||||
uint32 m_lastGotoOffset{0};
|
||||
};
|
||||
48
src/gui/debugger/DumpWindow.cpp
Normal file
48
src/gui/debugger/DumpWindow.cpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#include "gui/wxgui.h"
|
||||
#include "gui/debugger/DumpWindow.h"
|
||||
|
||||
#include "gui/debugger/DebuggerWindow2.h"
|
||||
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
|
||||
#include "gui/debugger/DumpCtrl.h"
|
||||
|
||||
enum
|
||||
{
|
||||
// REGISTER
|
||||
REGISTER_ADDRESS_R0 = wxID_HIGHEST + 8200,
|
||||
REGISTER_LABEL_R0 = REGISTER_ADDRESS_R0 + 32,
|
||||
REGISTER_LABEL_FPR0_0 = REGISTER_LABEL_R0 + 32,
|
||||
REGISTER_LABEL_FPR1_0 = REGISTER_LABEL_R0 + 32,
|
||||
};
|
||||
|
||||
DumpWindow::DumpWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size)
|
||||
: wxFrame(&parent, wxID_ANY, wxT("Memory Dump"), wxDefaultPosition, wxSize(600, 250), wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxFRAME_FLOAT_ON_PARENT)
|
||||
{
|
||||
this->wxWindowBase::SetBackgroundColour(*wxWHITE);
|
||||
|
||||
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_dump_ctrl = new DumpCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxScrolledWindowStyle);
|
||||
main_sizer->Add(m_dump_ctrl, 1, wxEXPAND);
|
||||
|
||||
this->SetSizer(main_sizer);
|
||||
this->wxWindowBase::Layout();
|
||||
|
||||
this->Centre(wxBOTH);
|
||||
|
||||
if (parent.GetConfig().data().pin_to_main)
|
||||
OnMainMove(main_position, main_size);
|
||||
}
|
||||
|
||||
void DumpWindow::OnMainMove(const wxPoint& main_position, const wxSize& main_size)
|
||||
{
|
||||
wxSize size(600, 250);
|
||||
this->SetSize(size);
|
||||
|
||||
wxPoint position = main_position;
|
||||
position.y += main_size.GetHeight();
|
||||
this->SetPosition(position);
|
||||
}
|
||||
|
||||
void DumpWindow::OnGameLoaded()
|
||||
{
|
||||
m_dump_ctrl->Init();
|
||||
}
|
||||
18
src/gui/debugger/DumpWindow.h
Normal file
18
src/gui/debugger/DumpWindow.h
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include "gui/debugger/DumpCtrl.h"
|
||||
|
||||
class DebuggerWindow2;
|
||||
|
||||
class DumpWindow : public wxFrame
|
||||
{
|
||||
public:
|
||||
DumpWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size);
|
||||
|
||||
void OnMainMove(const wxPoint& position, const wxSize& main_size);
|
||||
void OnGameLoaded();
|
||||
|
||||
private:
|
||||
wxScrolledWindow* m_scrolled_window;
|
||||
DumpCtrl* m_dump_ctrl;
|
||||
};
|
||||
137
src/gui/debugger/ModuleWindow.cpp
Normal file
137
src/gui/debugger/ModuleWindow.cpp
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
#include "gui/wxgui.h"
|
||||
#include "gui/guiWrapper.h"
|
||||
#include "gui/debugger/ModuleWindow.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "gui/debugger/DebuggerWindow2.h"
|
||||
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
|
||||
|
||||
#include "Cafe/OS/RPL/rpl.h"
|
||||
#include "Cafe/OS/RPL/rpl_structs.h"
|
||||
|
||||
#include "Cafe/GraphicPack/GraphicPack2.h"
|
||||
|
||||
enum ItemColumns
|
||||
{
|
||||
ColumnName = 0,
|
||||
ColumnAddress,
|
||||
ColumnSize,
|
||||
};
|
||||
|
||||
ModuleWindow::ModuleWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size)
|
||||
: wxFrame(&parent, wxID_ANY, "Modules", wxDefaultPosition, wxSize(420, 250), wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxFRAME_FLOAT_ON_PARENT)
|
||||
{
|
||||
this->SetSizeHints(wxDefaultSize, wxDefaultSize);
|
||||
|
||||
this->wxWindowBase::SetBackgroundColour(*wxWHITE);
|
||||
|
||||
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_modules = new wxListView(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT);
|
||||
|
||||
wxListItem col0;
|
||||
col0.SetId(ColumnName);
|
||||
col0.SetText(_("Name"));
|
||||
col0.SetWidth(125);
|
||||
m_modules->InsertColumn(ColumnName, col0);
|
||||
|
||||
wxListItem col1;
|
||||
col1.SetId(ColumnAddress);
|
||||
col1.SetText(_("Address"));
|
||||
col1.SetWidth(75);
|
||||
col1.SetAlign(wxLIST_FORMAT_RIGHT);
|
||||
m_modules->InsertColumn(ColumnAddress, col1);
|
||||
|
||||
wxListItem col2;
|
||||
col2.SetId(ColumnSize);
|
||||
col2.SetText(_("Size"));
|
||||
col2.SetWidth(75);
|
||||
col2.SetAlign(wxLIST_FORMAT_RIGHT);
|
||||
m_modules->InsertColumn(ColumnSize, col2);
|
||||
|
||||
main_sizer->Add(m_modules, 1, wxEXPAND);
|
||||
|
||||
this->SetSizer(main_sizer);
|
||||
this->wxWindowBase::Layout();
|
||||
|
||||
this->Centre(wxBOTH);
|
||||
|
||||
if (parent.GetConfig().data().pin_to_main)
|
||||
OnMainMove(main_position, main_size);
|
||||
|
||||
m_modules->Bind(wxEVT_LEFT_DCLICK, &ModuleWindow::OnLeftDClick, this);
|
||||
|
||||
OnGameLoaded();
|
||||
}
|
||||
|
||||
void ModuleWindow::OnMainMove(const wxPoint& main_position, const wxSize& main_size)
|
||||
{
|
||||
wxSize size(420, 250);
|
||||
this->SetSize(size);
|
||||
|
||||
wxPoint position = main_position;
|
||||
position.x -= 420;
|
||||
position.y += main_size.y;
|
||||
this->SetPosition(position);
|
||||
}
|
||||
|
||||
|
||||
void ModuleWindow::OnGameLoaded()
|
||||
{
|
||||
Freeze();
|
||||
|
||||
m_modules->DeleteAllItems();
|
||||
|
||||
const auto module_count = RPLLoader_GetModuleCount();
|
||||
const auto module_list = RPLLoader_GetModuleList();
|
||||
for (int i = 0; i < module_count; i++)
|
||||
{
|
||||
const auto module = module_list[i];
|
||||
if (module)
|
||||
{
|
||||
wxListItem item;
|
||||
item.SetId(i);
|
||||
item.SetText(module->moduleName2.c_str());
|
||||
|
||||
const auto index = m_modules->InsertItem(item);
|
||||
m_modules->SetItem(index, ColumnAddress, wxString::Format("%08x", module->regionMappingBase_text.GetMPTR()));
|
||||
m_modules->SetItem(index, ColumnSize, wxString::Format("%x", module->regionSize_text));
|
||||
}
|
||||
}
|
||||
|
||||
sint32 patch_count = 0;
|
||||
for (auto& gfx_pack : GraphicPack2::GetGraphicPacks())
|
||||
{
|
||||
for (auto& patch_group : gfx_pack->GetPatchGroups())
|
||||
{
|
||||
if (patch_group->isApplied())
|
||||
{
|
||||
wxListItem item;
|
||||
item.SetId(module_count + patch_count);
|
||||
item.SetText(std::string(patch_group->getName()));
|
||||
|
||||
const auto index = m_modules->InsertItem(item);
|
||||
m_modules->SetItem(index, ColumnAddress, wxString::Format("%08x", patch_group->getCodeCaveBase()));
|
||||
m_modules->SetItem(index, ColumnSize, wxString::Format("%x", patch_group->getCodeCaveSize()));
|
||||
patch_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Thaw();
|
||||
}
|
||||
|
||||
|
||||
void ModuleWindow::OnLeftDClick(wxMouseEvent& event)
|
||||
{
|
||||
long selected = m_modules->GetFirstSelected();
|
||||
if (selected == -1)
|
||||
return;
|
||||
const auto text = m_modules->GetItemText(selected, ColumnAddress);
|
||||
const auto address = std::stoul(text.ToStdString(), nullptr, 16);
|
||||
if (address == 0)
|
||||
return;
|
||||
debuggerState.debugSession.instructionPointer = address;
|
||||
debuggerWindow_moveIP();
|
||||
}
|
||||
17
src/gui/debugger/ModuleWindow.h
Normal file
17
src/gui/debugger/ModuleWindow.h
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
class DebuggerWindow2;
|
||||
|
||||
class ModuleWindow : public wxFrame
|
||||
{
|
||||
public:
|
||||
ModuleWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size);
|
||||
|
||||
void OnMainMove(const wxPoint& position, const wxSize& main_size);
|
||||
void OnGameLoaded();
|
||||
|
||||
private:
|
||||
void OnLeftDClick(wxMouseEvent& event);
|
||||
|
||||
wxListView* m_modules;
|
||||
};
|
||||
255
src/gui/debugger/RegisterCtrl.cpp
Normal file
255
src/gui/debugger/RegisterCtrl.cpp
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
#include "gui/wxgui.h"
|
||||
#include "gui/debugger/RegisterCtrl.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "Cafe/OS/RPL/rpl_structs.h"
|
||||
#include "Cafe/OS/RPL/rpl.h"
|
||||
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
|
||||
#include "gui/debugger/DebuggerWindow2.h"
|
||||
|
||||
#define COLOR_DEBUG_ACTIVE_BP 0xFFFFA0FF
|
||||
#define COLOR_DEBUG_ACTIVE 0xFFFFA080
|
||||
#define COLOR_DEBUG_BP 0xFF8080FF
|
||||
|
||||
#define SYNTAX_COLOR_GPR 0xFF000066
|
||||
#define SYNTAX_COLOR_FPR 0xFF006666
|
||||
#define SYNTAX_COLOR_SPR 0xFF666600
|
||||
#define SYNTAX_COLOR_CR 0xFF666600
|
||||
#define SYNTAX_COLOR_IMM 0xFF006600
|
||||
#define SYNTAX_COLOR_IMM_OFFSET 0xFF006600
|
||||
#define SYNTAX_COLOR_CIMM 0xFF880000
|
||||
#define SYNTAX_COLOR_PSEUDO 0xFFA0A0A0 // color for pseudo code
|
||||
#define SYNTAX_COLOR_SYMBOL 0xFF0000A0 // color for function symbol
|
||||
|
||||
#define OFFSET_REGISTER (60)
|
||||
#define OFFSET_REGISTER_LABEL (100)
|
||||
#define OFFSET_FPR (120)
|
||||
|
||||
RegisterCtrl::RegisterCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size, long style)
|
||||
: TextList(parent, id, pos, size, style), m_prev_snapshot({})
|
||||
{
|
||||
this->SetElementCount(32 + 1 + 32);
|
||||
RefreshControl();
|
||||
}
|
||||
|
||||
void RegisterCtrl::OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position)
|
||||
{
|
||||
wxPoint position = start_position;
|
||||
|
||||
//RPLModule* current_rpl_module = rpl3_getModuleByCodeAddr(MEMORY_CODEAREA_ADDR + start);
|
||||
for (sint32 i = 0; i <= count; i++)
|
||||
{
|
||||
const uint32 line = start + i;
|
||||
|
||||
if (line < 32)
|
||||
{
|
||||
dc.SetTextForeground(COLOR_BLACK);
|
||||
dc.DrawText(wxString::Format("R%d", line), position);
|
||||
position.x += OFFSET_REGISTER;
|
||||
|
||||
const uint32 register_value = debuggerState.debugSession.ppcSnapshot.gpr[line];
|
||||
if(register_value != m_prev_snapshot.gpr[line])
|
||||
dc.SetTextForeground(COLOR_RED);
|
||||
else
|
||||
dc.SetTextForeground(COLOR_BLACK);
|
||||
|
||||
dc.DrawText(wxString::Format("%08x", register_value), position);
|
||||
position.x += OFFSET_REGISTER_LABEL;
|
||||
|
||||
// check if address is a code offset
|
||||
RPLModule* code_module = RPLLoader_FindModuleByCodeAddr(register_value);
|
||||
if (code_module)
|
||||
dc.DrawText(wxString::Format("<%s> + %x", code_module->moduleName2.c_str(), register_value - code_module->regionMappingBase_text.GetMPTR()), position);
|
||||
|
||||
// check if address is a string
|
||||
uint32 string_offset = register_value;
|
||||
if (string_offset >= MEMORY_DATA_AREA_ADDR && string_offset < (MEMORY_DATA_AREA_ADDR+MEMORY_DATA_AREA_SIZE) )
|
||||
{
|
||||
bool is_valid_string = true;
|
||||
std::stringstream buffer;
|
||||
|
||||
uint32 string_offset = register_value;
|
||||
while (string_offset < (MEMORY_DATA_AREA_ADDR + MEMORY_DATA_AREA_SIZE))
|
||||
{
|
||||
const uint8 c = memory_readU8(string_offset++);
|
||||
if (isprint(c))
|
||||
{
|
||||
buffer << c;
|
||||
}
|
||||
else if( c == '\0' )
|
||||
{
|
||||
buffer << c;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
is_valid_string = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(is_valid_string && buffer.tellp() > 1)
|
||||
{
|
||||
dc.DrawText(wxString::Format("s \"%s\"", buffer.str().c_str()), position);
|
||||
}
|
||||
else
|
||||
{
|
||||
// check for widestring
|
||||
is_valid_string = true;
|
||||
string_offset = register_value;
|
||||
std::wstringstream wbuffer;
|
||||
while (string_offset < (MEMORY_DATA_AREA_ADDR + MEMORY_DATA_AREA_SIZE))
|
||||
{
|
||||
const uint16 c = memory_readU16(string_offset);
|
||||
string_offset += 2;
|
||||
|
||||
if (iswprint(c))
|
||||
{
|
||||
wbuffer << c;
|
||||
}
|
||||
else if (c == L'\0')
|
||||
{
|
||||
wbuffer << c;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
is_valid_string = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_valid_string && buffer.tellp() > 1)
|
||||
{
|
||||
dc.DrawText(wxString::Format(L"ws \"%s\"", wbuffer.str().c_str()), position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if( 32 + 1 <= line && line <= 32 + 1 + 32)
|
||||
{
|
||||
const uint32 register_index = line - 32 - 1;
|
||||
dc.SetTextForeground(COLOR_BLACK);
|
||||
dc.DrawText(wxString::Format("F0_%d", register_index), position);
|
||||
position.x += OFFSET_REGISTER;
|
||||
|
||||
const uint64 fpr0_value = debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp0int;
|
||||
if (fpr0_value != m_prev_snapshot.fpr[register_index].fp0int)
|
||||
dc.SetTextForeground(COLOR_RED);
|
||||
else
|
||||
dc.SetTextForeground(COLOR_BLACK);
|
||||
|
||||
//dc.DrawText(wxString::Format("%016llx", fpr0_value), position);
|
||||
dc.DrawText(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp0), position);
|
||||
|
||||
position.x += OFFSET_FPR;
|
||||
|
||||
dc.SetTextForeground(COLOR_BLACK);
|
||||
dc.DrawText(wxString::Format("F1_%d", register_index), position);
|
||||
position.x += OFFSET_REGISTER;
|
||||
|
||||
const uint64 fpr1_value = debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp1int;
|
||||
if (fpr1_value != m_prev_snapshot.fpr[register_index].fp1int)
|
||||
dc.SetTextForeground(COLOR_RED);
|
||||
else
|
||||
dc.SetTextForeground(COLOR_BLACK);
|
||||
|
||||
//dc.DrawText(wxString::Format("%016llx", fpr1_value), position);
|
||||
dc.DrawText(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp1), position);
|
||||
}
|
||||
else
|
||||
{
|
||||
// empty line
|
||||
}
|
||||
|
||||
NextLine(position, &start_position);
|
||||
}
|
||||
|
||||
memcpy(&m_prev_snapshot, &debuggerState.debugSession.ppcSnapshot, sizeof(m_prev_snapshot));
|
||||
}
|
||||
|
||||
void RegisterCtrl::OnMouseMove(const wxPoint& position, uint32 line)
|
||||
{
|
||||
if (!m_mouse_down)
|
||||
return;
|
||||
|
||||
wxPoint pos = position;
|
||||
if(pos.x <= OFFSET_REGISTER)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
pos.x -= OFFSET_REGISTER;
|
||||
|
||||
if (pos.x <= OFFSET_REGISTER_LABEL)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
pos.x -= OFFSET_REGISTER_LABEL;
|
||||
}
|
||||
|
||||
void RegisterCtrl::OnMouseDClick(const wxPoint& position, uint32 line)
|
||||
{
|
||||
if (!debuggerState.debugSession.isTrapped)
|
||||
return;
|
||||
|
||||
if(line < 32)
|
||||
{
|
||||
const uint32 register_index = line;
|
||||
if (position.x <= OFFSET_REGISTER + OFFSET_REGISTER_LABEL)
|
||||
{
|
||||
const uint32 register_value = debuggerState.debugSession.ppcSnapshot.gpr[register_index];
|
||||
wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), _(wxString::Format("Set R%d value", register_index)), wxString::Format("%08x", register_value));
|
||||
if (set_value_dialog.ShowModal() == wxID_OK)
|
||||
{
|
||||
const uint32 new_value = std::stoul(set_value_dialog.GetValue().ToStdString(), nullptr, 16);
|
||||
debuggerState.debugSession.hCPU->gpr[register_index] = new_value;
|
||||
debuggerState.debugSession.ppcSnapshot.gpr[register_index] = new_value;
|
||||
|
||||
wxRect update_rect(0, line * m_line_height, GetSize().x, m_line_height);
|
||||
RefreshControl(&update_rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
// one line empty between = line 32
|
||||
else if (32 + 1 <= line && line <= 32 + 1 + 32)
|
||||
{
|
||||
const uint32 register_index = line - 32 - 1;
|
||||
if (position.x <= OFFSET_REGISTER + OFFSET_FPR)
|
||||
{
|
||||
const double register_value = debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp0;
|
||||
wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), _(wxString::Format("Set FP0_%d value", register_index)), wxString::Format("%lf", register_value));
|
||||
if (set_value_dialog.ShowModal() == wxID_OK)
|
||||
{
|
||||
const double new_value = std::stod(set_value_dialog.GetValue().ToStdString());
|
||||
debuggerState.debugSession.hCPU->fpr[register_index].fp0 = new_value;
|
||||
debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp0 = new_value;
|
||||
|
||||
wxRect update_rect(0, line * m_line_height, GetSize().x, m_line_height);
|
||||
RefreshControl(&update_rect);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const double register_value = debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp1;
|
||||
wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), _(wxString::Format("Set FP1_%d value", register_index)), wxString::Format("%lf", register_value));
|
||||
if (set_value_dialog.ShowModal() == wxID_OK)
|
||||
{
|
||||
const double new_value = std::stod(set_value_dialog.GetValue().ToStdString());
|
||||
debuggerState.debugSession.hCPU->fpr[register_index].fp1 = new_value;
|
||||
debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp1 = new_value;
|
||||
|
||||
wxRect update_rect(0, line * m_line_height, GetSize().x, m_line_height);
|
||||
RefreshControl(&update_rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterCtrl::OnGameLoaded()
|
||||
{
|
||||
RefreshControl();
|
||||
}
|
||||
|
||||
18
src/gui/debugger/RegisterCtrl.h
Normal file
18
src/gui/debugger/RegisterCtrl.h
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
#include "gui/components/TextList.h"
|
||||
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
|
||||
|
||||
class RegisterCtrl : public TextList
|
||||
{
|
||||
public:
|
||||
RegisterCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size, long style);
|
||||
void OnGameLoaded();
|
||||
|
||||
protected:
|
||||
void OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position) override;
|
||||
void OnMouseMove(const wxPoint& position, uint32 line) override;
|
||||
void OnMouseDClick(const wxPoint& position, uint32 line) override;
|
||||
|
||||
private:
|
||||
PPCSnapshot m_prev_snapshot;
|
||||
};
|
||||
450
src/gui/debugger/RegisterWindow.cpp
Normal file
450
src/gui/debugger/RegisterWindow.cpp
Normal file
|
|
@ -0,0 +1,450 @@
|
|||
#include "gui/wxgui.h"
|
||||
#include "gui/debugger/RegisterWindow.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "gui/debugger/DebuggerWindow2.h"
|
||||
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
|
||||
#include "Cafe/OS/RPL/rpl.h"
|
||||
#include "Cafe/OS/RPL/rpl_structs.h"
|
||||
#include "Cafe/HW/Espresso/EspressoISA.h"
|
||||
|
||||
enum
|
||||
{
|
||||
// REGISTER
|
||||
kRegisterValueR0 = wxID_HIGHEST + 8400,
|
||||
kRegisterLabelR0 = kRegisterValueR0 + 32,
|
||||
kRegisterLabelLR = kRegisterLabelR0 + 32,
|
||||
kRegisterValueLR = kRegisterLabelLR + 1,
|
||||
kRegisterValueFPR0_0 = kRegisterValueLR + 32,
|
||||
kRegisterValueFPR1_0 = kRegisterValueFPR0_0 + 32,
|
||||
kRegisterValueCR0 = kRegisterValueFPR1_0 + 32,
|
||||
kContextMenuZero,
|
||||
kContextMenuInc,
|
||||
kContextMenuDec,
|
||||
kContextMenuCopy,
|
||||
kContextMenuGotoDisasm,
|
||||
kContextMenuGotoDump,
|
||||
};
|
||||
|
||||
RegisterWindow::RegisterWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size)
|
||||
: wxFrame(&parent, wxID_ANY, "Register View", wxDefaultPosition, wxSize(400, 975), wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxFRAME_FLOAT_ON_PARENT),
|
||||
m_prev_snapshot({}), m_show_double_values(true), m_context_ctrl(nullptr)
|
||||
{
|
||||
SetSizeHints(wxDefaultSize, wxDefaultSize);
|
||||
SetMaxSize({ 400, 975 });
|
||||
wxWindowBase::SetBackgroundColour(*wxWHITE);
|
||||
|
||||
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto scrolled_win = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto gpr_sizer = new wxFlexGridSizer(0, 3, 0, 0);
|
||||
|
||||
// GPRs
|
||||
for(sint32 i = 0; i < 32; ++i)
|
||||
{
|
||||
gpr_sizer->Add(new wxStaticText(scrolled_win, wxID_ANY, wxString::Format("R%d", i)), 0, wxLEFT, 5);
|
||||
|
||||
auto value = new wxTextCtrl(scrolled_win, kRegisterValueR0 + i, wxString::Format("%08x", 0), wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER);
|
||||
value->SetBackgroundColour(*wxWHITE);
|
||||
value->Bind(wxEVT_LEFT_DCLICK, &RegisterWindow::OnMouseDClickEvent, this);
|
||||
//value->Bind(wxEVT_CONTEXT_MENU, &RegisterWindow::OnValueContextMenu, this);
|
||||
gpr_sizer->Add(value, 0, wxLEFT|wxRIGHT, 5);
|
||||
|
||||
auto label = new wxTextCtrl(scrolled_win, kRegisterLabelR0 + i, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER);
|
||||
label->SetMinSize(wxSize(500, -1));
|
||||
label->SetBackgroundColour(*wxWHITE);
|
||||
gpr_sizer->Add(label, 0, wxEXPAND);
|
||||
}
|
||||
|
||||
{
|
||||
// LR
|
||||
gpr_sizer->Add(new wxStaticText(scrolled_win, wxID_ANY, wxString::Format("LR")), 0, wxLEFT, 5);
|
||||
auto value = new wxTextCtrl(scrolled_win, kRegisterValueLR, wxString::Format("%08x", 0), wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER);
|
||||
value->SetBackgroundColour(*wxWHITE);
|
||||
value->Bind(wxEVT_LEFT_DCLICK, &RegisterWindow::OnMouseDClickEvent, this);
|
||||
//value->Bind(wxEVT_CONTEXT_MENU, &RegisterWindow::OnValueContextMenu, this);
|
||||
gpr_sizer->Add(value, 0, wxLEFT | wxRIGHT, 5);
|
||||
auto label = new wxTextCtrl(scrolled_win, kRegisterLabelLR, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER);
|
||||
label->SetMinSize(wxSize(500, -1));
|
||||
label->SetBackgroundColour(*wxWHITE);
|
||||
gpr_sizer->Add(label, 0, wxEXPAND);
|
||||
}
|
||||
|
||||
sizer->Add(gpr_sizer, 1, wxEXPAND);
|
||||
|
||||
auto button = new wxButton(scrolled_win, wxID_ANY, "FP view mode");
|
||||
button->Bind(wxEVT_BUTTON, &RegisterWindow::OnFPViewModePress, this);
|
||||
sizer->Add(button, 0, wxALL, 5);
|
||||
|
||||
auto fp_sizer = new wxFlexGridSizer(0, 3, 0, 0);
|
||||
for (sint32 i = 0; i < 32; ++i)
|
||||
{
|
||||
fp_sizer->Add(new wxStaticText(scrolled_win, wxID_ANY, wxString::Format("FP%d", i)), 0, wxLEFT, 5);
|
||||
|
||||
auto value0 = new wxTextCtrl(scrolled_win, kRegisterValueFPR0_0 + i, wxString::Format("%lf", 0.0), wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER);
|
||||
value0->SetBackgroundColour(*wxWHITE);
|
||||
value0->Bind(wxEVT_LEFT_DCLICK, &RegisterWindow::OnMouseDClickEvent, this);
|
||||
fp_sizer->Add(value0, 0, wxLEFT | wxRIGHT, 5);
|
||||
|
||||
auto value1 = new wxTextCtrl(scrolled_win, kRegisterValueFPR1_0 + i, wxString::Format("%lf", 0.0), wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER);
|
||||
value1->SetBackgroundColour(*wxWHITE);
|
||||
value1->Bind(wxEVT_LEFT_DCLICK, &RegisterWindow::OnMouseDClickEvent, this);
|
||||
fp_sizer->Add(value1, 0, wxLEFT | wxRIGHT, 5);
|
||||
}
|
||||
|
||||
sizer->Add(fp_sizer, 0, wxEXPAND);
|
||||
auto cr_sizer = new wxFlexGridSizer(0, 2, 0, 0);
|
||||
|
||||
// CRs
|
||||
for (sint32 i = 0; i < 8; ++i)
|
||||
{
|
||||
cr_sizer->Add(new wxStaticText(scrolled_win, wxID_ANY, wxString::Format("CR%d", i)), 0, wxLEFT, 5);
|
||||
auto value = new wxTextCtrl(scrolled_win, kRegisterValueCR0 + i, "-", wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER);
|
||||
value->SetBackgroundColour(*wxWHITE);
|
||||
//value->Bind(wxEVT_CONTEXT_MENU, &RegisterWindow::OnValueContextMenu, this);
|
||||
cr_sizer->Add(value, 0, wxRIGHT, 5);
|
||||
}
|
||||
|
||||
sizer->Add(cr_sizer, 0, wxEXPAND);
|
||||
scrolled_win->SetSizerAndFit(sizer);
|
||||
scrolled_win->SetScrollRate(0, GetCharWidth());
|
||||
|
||||
main_sizer->Add(scrolled_win, 1, wxEXPAND);
|
||||
SetSizer(main_sizer);
|
||||
Layout();
|
||||
|
||||
if (parent.GetConfig().data().pin_to_main)
|
||||
OnMainMove(main_position, main_size);
|
||||
|
||||
//Bind(wxEVT_COMMAND_MENU_SELECTED, &RegisterWindow::OnValueContextMenuSelected, this, kContextMenuZero, kContextMenuGotoDump);
|
||||
}
|
||||
|
||||
|
||||
void RegisterWindow::OnMainMove(const wxPoint& main_position, const wxSize& main_size)
|
||||
{
|
||||
wxSize size(400, 255 + main_size.y + 250);
|
||||
this->SetSize(size);
|
||||
|
||||
wxPoint position = main_position;
|
||||
position.x += main_size.x;
|
||||
position.y -= 255;
|
||||
this->SetPosition(position);
|
||||
}
|
||||
|
||||
void RegisterWindow::UpdateIntegerRegister(wxTextCtrl* label, wxTextCtrl* value, uint32 registerValue, bool hasChanged)
|
||||
{
|
||||
//const auto value = dynamic_cast<wxTextCtrl*>(GetWindowChild(kRegisterValueR0 + i));
|
||||
//wxASSERT(value);
|
||||
|
||||
//const bool has_changed = register_value != m_prev_snapshot.gpr[i];
|
||||
if (hasChanged)
|
||||
value->SetForegroundColour(COLOR_RED);
|
||||
else if (value->GetForegroundColour() != COLOR_BLACK)
|
||||
value->SetForegroundColour(COLOR_BLACK);
|
||||
|
||||
value->SetLabelText(wxString::Format("%08x", registerValue));
|
||||
|
||||
//const auto label = dynamic_cast<wxTextCtrl*>(GetWindowChild(kRegisterLabelR0 + i));
|
||||
//wxASSERT(label);
|
||||
label->SetForegroundColour(hasChanged ? COLOR_RED : COLOR_BLACK);
|
||||
|
||||
// check if address is a string
|
||||
if (registerValue >= MEMORY_DATA_AREA_ADDR && registerValue < (MEMORY_DATA_AREA_ADDR + MEMORY_DATA_AREA_SIZE))
|
||||
{
|
||||
bool is_valid_string = true;
|
||||
std::stringstream buffer;
|
||||
|
||||
uint32 string_offset = registerValue;
|
||||
while (string_offset < (MEMORY_DATA_AREA_ADDR + MEMORY_DATA_AREA_SIZE))
|
||||
{
|
||||
const uint8 c = memory_readU8(string_offset++);
|
||||
if (isprint(c))
|
||||
buffer << c;
|
||||
else if (c == '\0')
|
||||
{
|
||||
buffer << c;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
is_valid_string = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_valid_string && buffer.tellp() > 1)
|
||||
{
|
||||
label->SetLabelText(wxString::Format("\"%s\"", buffer.str().c_str()));
|
||||
return;
|
||||
}
|
||||
|
||||
// check for widestring
|
||||
is_valid_string = true;
|
||||
string_offset = registerValue;
|
||||
std::wstringstream wbuffer;
|
||||
while (string_offset < (MEMORY_DATA_AREA_ADDR + MEMORY_DATA_AREA_SIZE - 1))
|
||||
{
|
||||
const uint16 c = memory_readU16(string_offset);
|
||||
string_offset += 2;
|
||||
|
||||
if (iswprint(c))
|
||||
wbuffer << c;
|
||||
else if (c == L'\0')
|
||||
{
|
||||
wbuffer << c;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
is_valid_string = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_valid_string && buffer.tellp() > 1)
|
||||
{
|
||||
label->SetLabelText(wxString::Format(L"ws\"%s\"", wbuffer.str().c_str()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// check if address is a code offset
|
||||
RPLModule* code_module = RPLLoader_FindModuleByCodeAddr(registerValue);
|
||||
if (code_module)
|
||||
{
|
||||
label->SetLabelText(wxString::Format("<%s> + %x", code_module->moduleName2.c_str(), registerValue - code_module->regionMappingBase_text.GetMPTR()));
|
||||
return;
|
||||
}
|
||||
|
||||
label->SetLabelText(wxEmptyString);
|
||||
}
|
||||
|
||||
void RegisterWindow::OnUpdateView()
|
||||
{
|
||||
// m_register_ctrl->RefreshControl();
|
||||
for (int i = 0; i < 32; ++i)
|
||||
{
|
||||
const uint32 registerValue = debuggerState.debugSession.ppcSnapshot.gpr[i];
|
||||
const bool hasChanged = registerValue != m_prev_snapshot.gpr[i];
|
||||
const auto value = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterValueR0 + i));
|
||||
wxASSERT(value);
|
||||
const auto label = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterLabelR0 + i));
|
||||
wxASSERT(label);
|
||||
|
||||
UpdateIntegerRegister(label, value, registerValue, hasChanged);
|
||||
}
|
||||
|
||||
// update LR
|
||||
{
|
||||
const uint32 registerValue = debuggerState.debugSession.ppcSnapshot.spr_lr;
|
||||
const bool hasChanged = registerValue != m_prev_snapshot.spr_lr;
|
||||
const auto value = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterValueLR));
|
||||
wxASSERT(value);
|
||||
const auto label = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterLabelLR));
|
||||
wxASSERT(label);
|
||||
UpdateIntegerRegister(label, value, registerValue, hasChanged);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 32; ++i)
|
||||
{
|
||||
const uint64_t register_value = debuggerState.debugSession.ppcSnapshot.fpr[i].fp0int;
|
||||
|
||||
const auto value = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterValueFPR0_0 + i));
|
||||
wxASSERT(value);
|
||||
|
||||
const bool has_changed = register_value != m_prev_snapshot.fpr[i].fp0int;
|
||||
if (has_changed)
|
||||
value->SetForegroundColour(COLOR_RED);
|
||||
else if (value->GetForegroundColour() != COLOR_BLACK)
|
||||
value->SetForegroundColour(COLOR_BLACK);
|
||||
else
|
||||
continue;
|
||||
|
||||
if(m_show_double_values)
|
||||
value->SetLabelText(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp0));
|
||||
else
|
||||
value->SetLabelText(wxString::Format("%016llx", register_value));
|
||||
}
|
||||
|
||||
for (int i = 0; i < 32; ++i)
|
||||
{
|
||||
const uint64_t register_value = debuggerState.debugSession.ppcSnapshot.fpr[i].fp1int;
|
||||
|
||||
const auto value = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterValueFPR1_0 + i));
|
||||
wxASSERT(value);
|
||||
|
||||
const bool has_changed = register_value != m_prev_snapshot.fpr[i].fp1int;
|
||||
if (has_changed)
|
||||
value->SetForegroundColour(COLOR_RED);
|
||||
else if (value->GetForegroundColour() != COLOR_BLACK)
|
||||
value->SetForegroundColour(COLOR_BLACK);
|
||||
else
|
||||
continue;
|
||||
|
||||
if (m_show_double_values)
|
||||
value->SetLabelText(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp1));
|
||||
else
|
||||
value->SetLabelText(wxString::Format("%016llx", register_value));
|
||||
}
|
||||
|
||||
// update CRs
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
const auto value = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterValueCR0 + i));
|
||||
wxASSERT(value);
|
||||
|
||||
auto cr_bits_ptr = debuggerState.debugSession.ppcSnapshot.cr + i * 4;
|
||||
auto cr_bits_ptr_cmp = m_prev_snapshot.cr + i * 4;
|
||||
|
||||
const bool has_changed = !std::equal(cr_bits_ptr, cr_bits_ptr + 4, cr_bits_ptr_cmp);
|
||||
if (has_changed)
|
||||
value->SetForegroundColour(COLOR_RED);
|
||||
else if (value->GetForegroundColour() != COLOR_BLACK)
|
||||
value->SetForegroundColour(COLOR_BLACK);
|
||||
else
|
||||
continue;
|
||||
|
||||
std::vector<std::string> joinArray = {};
|
||||
if (cr_bits_ptr[Espresso::CR_BIT_INDEX_LT] != 0)
|
||||
joinArray.emplace_back("LT");
|
||||
if (cr_bits_ptr[Espresso::CR_BIT_INDEX_GT] != 0)
|
||||
joinArray.emplace_back("GT");
|
||||
if (cr_bits_ptr[Espresso::CR_BIT_INDEX_EQ] != 0)
|
||||
joinArray.emplace_back("EQ");
|
||||
if (cr_bits_ptr[Espresso::CR_BIT_INDEX_SO] != 0)
|
||||
joinArray.emplace_back("SO");
|
||||
|
||||
if (joinArray.empty())
|
||||
value->SetLabelText("-");
|
||||
else
|
||||
value->SetLabelText(fmt::format("{}", fmt::join(joinArray, ", ")));
|
||||
}
|
||||
|
||||
memcpy(&m_prev_snapshot, &debuggerState.debugSession.ppcSnapshot, sizeof(m_prev_snapshot));
|
||||
}
|
||||
|
||||
void RegisterWindow::OnMouseDClickEvent(wxMouseEvent& event)
|
||||
{
|
||||
if (!debuggerState.debugSession.isTrapped)
|
||||
{
|
||||
event.Skip();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto id = event.GetId();
|
||||
if(kRegisterValueR0 <= id && id < kRegisterValueR0 + 32)
|
||||
{
|
||||
const uint32 register_index = id - kRegisterValueR0;
|
||||
const uint32 register_value = debuggerState.debugSession.ppcSnapshot.gpr[register_index];
|
||||
wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), _(wxString::Format("Set R%d value", register_index)), wxString::Format("%08x", register_value));
|
||||
if (set_value_dialog.ShowModal() == wxID_OK)
|
||||
{
|
||||
const uint32 new_value = std::stoul(set_value_dialog.GetValue().ToStdString(), nullptr, 16);
|
||||
debuggerState.debugSession.hCPU->gpr[register_index] = new_value;
|
||||
debuggerState.debugSession.ppcSnapshot.gpr[register_index] = new_value;
|
||||
OnUpdateView();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (kRegisterValueFPR0_0 <= id && id < kRegisterValueFPR0_0 + 32)
|
||||
{
|
||||
const uint32 register_index = id - kRegisterValueFPR0_0;
|
||||
const double register_value = debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp0;
|
||||
wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), _(wxString::Format("Set FP0_%d value", register_index)), wxString::Format("%lf", register_value));
|
||||
if (set_value_dialog.ShowModal() == wxID_OK)
|
||||
{
|
||||
const double new_value = std::stod(set_value_dialog.GetValue().ToStdString());
|
||||
debuggerState.debugSession.hCPU->fpr[register_index].fp0 = new_value;
|
||||
debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp0 = new_value;
|
||||
OnUpdateView();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (kRegisterValueFPR1_0 <= id && id < kRegisterValueFPR1_0 + 32)
|
||||
{
|
||||
const uint32 register_index = id - kRegisterValueFPR1_0;
|
||||
const double register_value = debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp1;
|
||||
wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), _(wxString::Format("Set FP1_%d value", register_index)), wxString::Format("%lf", register_value));
|
||||
if (set_value_dialog.ShowModal() == wxID_OK)
|
||||
{
|
||||
const double new_value = std::stod(set_value_dialog.GetValue().ToStdString());
|
||||
debuggerState.debugSession.hCPU->fpr[register_index].fp1 = new_value;
|
||||
debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp1 = new_value;
|
||||
OnUpdateView();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void RegisterWindow::OnFPViewModePress(wxCommandEvent& event)
|
||||
{
|
||||
m_show_double_values = !m_show_double_values;
|
||||
|
||||
for (int i = 0; i < 32; ++i)
|
||||
{
|
||||
const auto value0 = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterValueFPR0_0 + i));
|
||||
const auto value1 = dynamic_cast<wxTextCtrl*>(FindWindow(kRegisterValueFPR1_0 + i));
|
||||
wxASSERT(value0);
|
||||
wxASSERT(value1);
|
||||
|
||||
if (m_show_double_values)
|
||||
{
|
||||
value0->SetLabelText(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp0));
|
||||
value1->SetLabelText(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp1));
|
||||
}
|
||||
else
|
||||
{
|
||||
value0->SetLabelText(wxString::Format("%016llx", debuggerState.debugSession.ppcSnapshot.fpr[i].fp0int));
|
||||
value1->SetLabelText(wxString::Format("%016llx", debuggerState.debugSession.ppcSnapshot.fpr[i].fp1int));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterWindow::OnValueContextMenu(wxContextMenuEvent& event)
|
||||
{
|
||||
wxMenu menu;
|
||||
|
||||
menu.Append(kContextMenuZero, _("&Zero"));
|
||||
menu.Append(kContextMenuInc, _("&Increment"));
|
||||
menu.Append(kContextMenuDec, _("&Decrement"));
|
||||
menu.AppendSeparator();
|
||||
menu.Append(kContextMenuCopy, _("&Copy"));
|
||||
menu.AppendSeparator();
|
||||
menu.Append(kContextMenuGotoDisasm, _("&Goto Disasm"));
|
||||
menu.Append(kContextMenuGotoDump, _("G&oto Dump"));
|
||||
|
||||
m_context_ctrl = dynamic_cast<wxTextCtrl*>(event.GetEventObject());
|
||||
|
||||
PopupMenu(&menu);
|
||||
}
|
||||
|
||||
void RegisterWindow::OnValueContextMenuSelected(wxCommandEvent& event)
|
||||
{
|
||||
wxASSERT(m_context_ctrl);
|
||||
|
||||
switch (event.GetId())
|
||||
{
|
||||
case kContextMenuZero:
|
||||
break;
|
||||
case kContextMenuInc:
|
||||
break;
|
||||
case kContextMenuDec:
|
||||
break;
|
||||
case kContextMenuCopy:
|
||||
break;
|
||||
case kContextMenuGotoDisasm:
|
||||
break;
|
||||
case kContextMenuGotoDump:
|
||||
break;
|
||||
}
|
||||
}
|
||||
26
src/gui/debugger/RegisterWindow.h
Normal file
26
src/gui/debugger/RegisterWindow.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
|
||||
|
||||
class DebuggerWindow2;
|
||||
|
||||
class RegisterWindow : public wxFrame
|
||||
{
|
||||
public:
|
||||
RegisterWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size);
|
||||
|
||||
void OnMainMove(const wxPoint& position, const wxSize& main_size);
|
||||
void OnUpdateView();
|
||||
|
||||
private:
|
||||
void OnMouseDClickEvent(wxMouseEvent& event);
|
||||
void OnFPViewModePress(wxCommandEvent& event);
|
||||
void OnValueContextMenu(wxContextMenuEvent& event);
|
||||
void OnValueContextMenuSelected(wxCommandEvent& event);
|
||||
|
||||
void UpdateIntegerRegister(wxTextCtrl* label, wxTextCtrl* value, uint32 registerValue, bool hasChanged);
|
||||
|
||||
PPCSnapshot m_prev_snapshot;
|
||||
bool m_show_double_values;
|
||||
|
||||
wxTextCtrl* m_context_ctrl;
|
||||
};
|
||||
163
src/gui/debugger/SymbolCtrl.cpp
Normal file
163
src/gui/debugger/SymbolCtrl.cpp
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
#include "gui/debugger/SymbolCtrl.h"
|
||||
#include "gui/guiWrapper.h"
|
||||
#include "Cafe/OS/RPL/rpl_symbol_storage.h"
|
||||
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
|
||||
|
||||
enum ItemColumns
|
||||
{
|
||||
ColumnName = 0,
|
||||
ColumnAddress,
|
||||
ColumnModule,
|
||||
};
|
||||
|
||||
SymbolListCtrl::SymbolListCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size) :
|
||||
wxListCtrl(parent, id, pos, size, wxLC_REPORT | wxLC_VIRTUAL)
|
||||
{
|
||||
wxListItem col0;
|
||||
col0.SetId(ColumnName);
|
||||
col0.SetText(_("Name"));
|
||||
col0.SetWidth(200);
|
||||
InsertColumn(ColumnName, col0);
|
||||
|
||||
wxListItem col1;
|
||||
col1.SetId(ColumnAddress);
|
||||
col1.SetText(_("Address"));
|
||||
col1.SetWidth(75);
|
||||
col1.SetAlign(wxLIST_FORMAT_RIGHT);
|
||||
InsertColumn(ColumnAddress, col1);
|
||||
|
||||
wxListItem col2;
|
||||
col2.SetId(ColumnModule);
|
||||
col2.SetText(_("Module"));
|
||||
col2.SetWidth(75);
|
||||
col2.SetAlign(wxLIST_FORMAT_RIGHT);
|
||||
InsertColumn(ColumnModule, col2);
|
||||
|
||||
Bind(wxEVT_LIST_ITEM_ACTIVATED, &SymbolListCtrl::OnLeftDClick, this);
|
||||
Bind(wxEVT_LIST_ITEM_RIGHT_CLICK, &SymbolListCtrl::OnRightClick, this);
|
||||
|
||||
m_list_filter = "";
|
||||
|
||||
OnGameLoaded();
|
||||
|
||||
SetItemCount(m_data.size());
|
||||
}
|
||||
|
||||
void SymbolListCtrl::OnGameLoaded()
|
||||
{
|
||||
m_data.clear();
|
||||
long itemId = 0;
|
||||
const auto symbol_map = rplSymbolStorage_lockSymbolMap();
|
||||
for (auto const& [address, symbol_info] : symbol_map)
|
||||
{
|
||||
if (symbol_info == nullptr || symbol_info->symbolName == nullptr)
|
||||
continue;
|
||||
|
||||
auto new_entry = m_data.try_emplace(
|
||||
symbol_info->address,
|
||||
(char*)(symbol_info->symbolName),
|
||||
(char*)(symbol_info->libName),
|
||||
"",
|
||||
false
|
||||
);
|
||||
|
||||
new_entry.first->second.searchName += new_entry.first->second.name;
|
||||
new_entry.first->second.searchName += new_entry.first->second.libName;
|
||||
new_entry.first->second.searchName.MakeLower();
|
||||
|
||||
if (m_list_filter == "")
|
||||
new_entry.first->second.visible = true;
|
||||
else if (new_entry.first->second.searchName.Contains(m_list_filter))
|
||||
new_entry.first->second.visible = true;
|
||||
}
|
||||
rplSymbolStorage_unlockSymbolMap();
|
||||
|
||||
SetItemCount(m_data.size());
|
||||
RefreshItems(GetTopItem(), GetTopItem() + GetCountPerPage() + 1);
|
||||
}
|
||||
|
||||
wxString SymbolListCtrl::OnGetItemText(long item, long column) const
|
||||
{
|
||||
if (item >= GetItemCount())
|
||||
return wxEmptyString;
|
||||
|
||||
long visible_idx = 0;
|
||||
for (const auto& [address, symbol] : m_data)
|
||||
{
|
||||
if (!symbol.visible)
|
||||
continue;
|
||||
|
||||
if (item == visible_idx)
|
||||
{
|
||||
if (column == ColumnName)
|
||||
return wxString(symbol.name);
|
||||
if (column == ColumnAddress)
|
||||
return wxString::Format("%08x", address);
|
||||
if (column == ColumnModule)
|
||||
return wxString(symbol.libName);
|
||||
}
|
||||
visible_idx++;
|
||||
}
|
||||
|
||||
return wxEmptyString;
|
||||
}
|
||||
|
||||
|
||||
void SymbolListCtrl::OnLeftDClick(wxListEvent& event)
|
||||
{
|
||||
long selected = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
|
||||
if (selected == -1)
|
||||
return;
|
||||
const auto text = GetItemText(selected, ColumnAddress);
|
||||
const auto address = std::stoul(text.ToStdString(), nullptr, 16);
|
||||
if (address == 0)
|
||||
return;
|
||||
debuggerState.debugSession.instructionPointer = address;
|
||||
debuggerWindow_moveIP();
|
||||
}
|
||||
|
||||
void SymbolListCtrl::OnRightClick(wxListEvent& event)
|
||||
{
|
||||
long selected = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
|
||||
if (selected == -1)
|
||||
return;
|
||||
auto text = GetItemText(selected, ColumnAddress);
|
||||
text = "0x" + text;
|
||||
#if BOOST_OS_WINDOWS
|
||||
if (OpenClipboard(nullptr))
|
||||
{
|
||||
EmptyClipboard();
|
||||
const HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, text.size()+1);
|
||||
if (hGlobal)
|
||||
{
|
||||
memcpy(GlobalLock(hGlobal), text.c_str(), text.size() + 1);
|
||||
GlobalUnlock(hGlobal);
|
||||
|
||||
SetClipboardData(CF_TEXT, hGlobal);
|
||||
GlobalFree(hGlobal);
|
||||
}
|
||||
CloseClipboard();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void SymbolListCtrl::ChangeListFilter(std::string filter)
|
||||
{
|
||||
m_list_filter = wxString(filter).MakeLower();
|
||||
|
||||
size_t visible_entries = m_data.size();
|
||||
for (auto& [address, symbol] : m_data)
|
||||
{
|
||||
if (m_list_filter == "")
|
||||
symbol.visible = true;
|
||||
else if (symbol.searchName.Contains(m_list_filter))
|
||||
symbol.visible = true;
|
||||
else
|
||||
{
|
||||
visible_entries--;
|
||||
symbol.visible = false;
|
||||
}
|
||||
}
|
||||
SetItemCount(visible_entries);
|
||||
RefreshItems(GetTopItem(), GetTopItem() + GetCountPerPage() + 1);
|
||||
}
|
||||
30
src/gui/debugger/SymbolCtrl.h
Normal file
30
src/gui/debugger/SymbolCtrl.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
#include <wx/listctrl.h>
|
||||
|
||||
class SymbolListCtrl : public wxListCtrl
|
||||
{
|
||||
public:
|
||||
SymbolListCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size);
|
||||
void OnGameLoaded();
|
||||
|
||||
void ChangeListFilter(std::string filter);
|
||||
private:
|
||||
struct SymbolItem {
|
||||
SymbolItem() {}
|
||||
SymbolItem(wxString name, wxString libName, wxString searchName, bool visible) : name(name), libName(libName), searchName(searchName), visible(visible) {}
|
||||
|
||||
|
||||
wxString name;
|
||||
wxString libName;
|
||||
wxString searchName;
|
||||
bool visible;
|
||||
};
|
||||
|
||||
std::map<MPTR, SymbolItem> m_data;
|
||||
wxString m_list_filter;
|
||||
|
||||
wxString OnGetItemText(long item, long column) const;
|
||||
void OnLeftDClick(wxListEvent& event);
|
||||
void OnRightClick(wxListEvent& event);
|
||||
};
|
||||
55
src/gui/debugger/SymbolWindow.cpp
Normal file
55
src/gui/debugger/SymbolWindow.cpp
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#include "gui/wxgui.h"
|
||||
#include "gui/guiWrapper.h"
|
||||
#include "gui/debugger/SymbolWindow.h"
|
||||
#include "gui/debugger/DebuggerWindow2.h"
|
||||
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
|
||||
#include "Cafe/OS/RPL/rpl_symbol_storage.h"
|
||||
|
||||
enum ItemColumns
|
||||
{
|
||||
ColumnName = 0,
|
||||
ColumnAddress,
|
||||
ColumnModule,
|
||||
};
|
||||
|
||||
SymbolWindow::SymbolWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size)
|
||||
: wxFrame(&parent, wxID_ANY, wxT("Symbol Window"), wxDefaultPosition, wxSize(600, 250), wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxFRAME_FLOAT_ON_PARENT)
|
||||
{
|
||||
this->wxWindowBase::SetBackgroundColour(*wxWHITE);
|
||||
|
||||
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_symbol_ctrl = new SymbolListCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
main_sizer->Add(m_symbol_ctrl, 1, wxEXPAND);
|
||||
|
||||
m_filter = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_NO_VSCROLL);
|
||||
m_filter->Bind(wxEVT_TEXT, &SymbolWindow::OnFilterChanged, this);
|
||||
main_sizer->Add(m_filter, 0, wxALL | wxEXPAND, 5);
|
||||
|
||||
this->SetSizer(main_sizer);
|
||||
this->wxWindowBase::Layout();
|
||||
|
||||
this->Centre(wxHORIZONTAL);
|
||||
|
||||
if (parent.GetConfig().data().pin_to_main)
|
||||
OnMainMove(main_position, main_size);
|
||||
}
|
||||
|
||||
void SymbolWindow::OnMainMove(const wxPoint& main_position, const wxSize& main_size)
|
||||
{
|
||||
wxSize size(420, 250);
|
||||
this->SetSize(size);
|
||||
|
||||
wxPoint position = main_position;
|
||||
position.x -= 420;
|
||||
this->SetPosition(position);
|
||||
}
|
||||
|
||||
void SymbolWindow::OnGameLoaded()
|
||||
{
|
||||
m_symbol_ctrl->OnGameLoaded();
|
||||
}
|
||||
|
||||
void SymbolWindow::OnFilterChanged(wxCommandEvent& event)
|
||||
{
|
||||
m_symbol_ctrl->ChangeListFilter(m_filter->GetValue().ToStdString());
|
||||
}
|
||||
22
src/gui/debugger/SymbolWindow.h
Normal file
22
src/gui/debugger/SymbolWindow.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include "gui/debugger/SymbolCtrl.h"
|
||||
|
||||
class DebuggerWindow2;
|
||||
|
||||
class SymbolWindow : public wxFrame
|
||||
{
|
||||
public:
|
||||
SymbolWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size);
|
||||
|
||||
void OnMainMove(const wxPoint& position, const wxSize& main_size);
|
||||
void OnGameLoaded();
|
||||
|
||||
void OnLeftDClick(wxListEvent& event);
|
||||
|
||||
private:
|
||||
wxTextCtrl* m_filter;
|
||||
SymbolListCtrl* m_symbol_ctrl;
|
||||
|
||||
void OnFilterChanged(wxCommandEvent& event);
|
||||
};
|
||||
100
src/gui/dialogs/CreateAccount/wxCreateAccountDialog.cpp
Normal file
100
src/gui/dialogs/CreateAccount/wxCreateAccountDialog.cpp
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
#include "gui/dialogs/CreateAccount/wxCreateAccountDialog.h"
|
||||
#include "Cafe/Account/Account.h"
|
||||
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/choice.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/msgdlg.h>
|
||||
#include "util/helpers/helpers.h"
|
||||
|
||||
wxCreateAccountDialog::wxCreateAccountDialog(wxWindow* parent)
|
||||
: wxDialog(parent, wxID_ANY, _("Create new account"))
|
||||
{
|
||||
auto* main_sizer = new wxFlexGridSizer(0, 2, 0, 0);
|
||||
main_sizer->AddGrowableCol(1);
|
||||
main_sizer->SetFlexibleDirection(wxBOTH);
|
||||
main_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
||||
|
||||
main_sizer->Add(new wxStaticText(this, wxID_ANY, wxT("PersistentId")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
|
||||
m_persistent_id = new wxTextCtrl(this, wxID_ANY, fmt::format("{:x}", Account::GetNextPersistentId()));
|
||||
m_persistent_id->SetToolTip(_("The persistent id is the internal folder name used for your saves. Only change this if you are importing saves from a Wii U with a specific id"));
|
||||
main_sizer->Add(m_persistent_id, 1, wxALL | wxEXPAND, 5);
|
||||
|
||||
main_sizer->Add(new wxStaticText(this, wxID_ANY, wxT("Mii name")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
|
||||
m_mii_name = new wxTextCtrl(this, wxID_ANY);
|
||||
m_mii_name->SetFocus();
|
||||
m_mii_name->SetMaxLength(10);
|
||||
main_sizer->Add(m_mii_name, 1, wxALL | wxEXPAND, 5);
|
||||
|
||||
main_sizer->Add(0, 0, 1, wxEXPAND, 5);
|
||||
{
|
||||
auto* button_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
m_ok_button = new wxButton(this, wxID_ANY, _("OK"));
|
||||
m_ok_button->Bind(wxEVT_BUTTON, &wxCreateAccountDialog::OnOK, this);
|
||||
button_sizer->Add(m_ok_button, 0, wxALL, 5);
|
||||
|
||||
m_cancel_buton = new wxButton(this, wxID_ANY, _("Cancel"));
|
||||
m_cancel_buton->Bind(wxEVT_BUTTON, &wxCreateAccountDialog::OnCancel, this);
|
||||
button_sizer->Add(m_cancel_buton, 0, wxALL, 5);
|
||||
|
||||
main_sizer->Add(button_sizer, 1, wxALIGN_RIGHT, 5);
|
||||
}
|
||||
|
||||
this->SetSizerAndFit(main_sizer);
|
||||
this->wxWindowBase::Layout();
|
||||
}
|
||||
|
||||
uint32 wxCreateAccountDialog::GetPersistentId() const
|
||||
{
|
||||
const std::string id_string = m_persistent_id->GetValue().c_str().AsChar();
|
||||
return ConvertString<uint32>(id_string, 16);
|
||||
}
|
||||
|
||||
wxString wxCreateAccountDialog::GetMiiName() const
|
||||
{
|
||||
return m_mii_name->GetValue();
|
||||
}
|
||||
|
||||
void wxCreateAccountDialog::OnOK(wxCommandEvent& event)
|
||||
{
|
||||
if(m_persistent_id->IsEmpty())
|
||||
{
|
||||
wxMessageBox(_("No persistent id entered!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto id = GetPersistentId();
|
||||
if(id < Account::kMinPersistendId)
|
||||
{
|
||||
wxMessageBox(fmt::format(_("The persistent id must be greater than {:x}!").ToStdString(), Account::kMinPersistendId),
|
||||
_("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& account = Account::GetAccount(id);
|
||||
if(account.GetPersistentId() == id)
|
||||
{
|
||||
const std::wstring msg = fmt::format(_("The persistent id {:x} is already in use by account {}!").ToStdWstring(),
|
||||
account.GetPersistentId(), std::wstring{ account.GetMiiName() });
|
||||
wxMessageBox(msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
return;
|
||||
}
|
||||
|
||||
if(m_mii_name->IsEmpty())
|
||||
{
|
||||
wxMessageBox(_("Account name may not be empty!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
return;
|
||||
}
|
||||
|
||||
EndModal(wxID_OK);
|
||||
}
|
||||
|
||||
void wxCreateAccountDialog::OnCancel(wxCommandEvent& event)
|
||||
{
|
||||
EndModal(wxID_CANCEL);
|
||||
}
|
||||
18
src/gui/dialogs/CreateAccount/wxCreateAccountDialog.h
Normal file
18
src/gui/dialogs/CreateAccount/wxCreateAccountDialog.h
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
#include <wx/dialog.h>
|
||||
|
||||
class wxCreateAccountDialog : public wxDialog
|
||||
{
|
||||
public:
|
||||
wxCreateAccountDialog(wxWindow* parent);
|
||||
|
||||
[[nodiscard]] uint32 GetPersistentId() const;
|
||||
[[nodiscard]] wxString GetMiiName() const;
|
||||
private:
|
||||
class wxTextCtrl* m_persistent_id;
|
||||
class wxTextCtrl* m_mii_name;
|
||||
class wxButton* m_ok_button, * m_cancel_buton;
|
||||
|
||||
void OnOK(wxCommandEvent& event);
|
||||
void OnCancel(wxCommandEvent& event);
|
||||
};
|
||||
330
src/gui/dialogs/SaveImport/SaveImportWindow.cpp
Normal file
330
src/gui/dialogs/SaveImport/SaveImportWindow.cpp
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
#include "SaveImportWindow.h"
|
||||
|
||||
#include "pugixml.hpp"
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/choice.h>
|
||||
#include <wx/combobox.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/frame.h>
|
||||
#include <wx/filepicker.h>
|
||||
#include <wx/msgdlg.h>
|
||||
#include "zip.h"
|
||||
|
||||
#include "Cafe/Account/Account.h"
|
||||
#include "config/ActiveSettings.h"
|
||||
#include "Cafe/OS/libs/coreinit/coreinit_Time.h"
|
||||
#include "util/helpers/helpers.h"
|
||||
#include "Cafe/HW/Espresso/PPCState.h"
|
||||
#include "gui/helpers/wxHelpers.h"
|
||||
|
||||
SaveImportWindow::SaveImportWindow(wxWindow* parent, uint64 title_id)
|
||||
: wxDialog(parent, wxID_ANY, _("Import save entry"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxFRAME_TOOL_WINDOW | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX),
|
||||
m_title_id(title_id)
|
||||
{
|
||||
auto* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
{
|
||||
auto* row1 = new wxFlexGridSizer(0, 2, 0, 0);
|
||||
row1->AddGrowableCol(1);
|
||||
|
||||
row1->Add(new wxStaticText(this, wxID_ANY, _("Source")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
m_source_selection = new wxFilePickerCtrl(this, wxID_ANY, wxEmptyString,
|
||||
_("Select a zipped save file"),
|
||||
wxStringFormat2(_("Import save entry {}"), "(*.zip)|*.zip"));
|
||||
m_source_selection->SetMinSize({ 270, -1 });
|
||||
row1->Add(m_source_selection, 1, wxALL | wxEXPAND, 5);
|
||||
|
||||
row1->Add(new wxStaticText(this, wxID_ANY, _("Target")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
|
||||
const auto& accounts = Account::GetAccounts();
|
||||
m_target_selection = new wxComboBox(this, wxID_ANY);
|
||||
for (const auto& account : accounts)
|
||||
{
|
||||
m_target_selection->Append(fmt::format("{:x} ({})", account.GetPersistentId(),
|
||||
boost::nowide::narrow(account.GetMiiName().data(), account.GetMiiName().size())),
|
||||
(void*)(uintptr_t)account.GetPersistentId());
|
||||
}
|
||||
row1->Add(m_target_selection, 1, wxALL | wxEXPAND, 5);
|
||||
|
||||
sizer->Add(row1, 0, wxEXPAND, 5);
|
||||
}
|
||||
|
||||
{
|
||||
auto* row2 = new wxFlexGridSizer(0, 2, 0, 0);
|
||||
row2->AddGrowableCol(1);
|
||||
row2->SetFlexibleDirection(wxBOTH);
|
||||
row2->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
||||
|
||||
auto* ok_button = new wxButton(this, wxID_ANY, _("OK"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
ok_button->Bind(wxEVT_BUTTON, &SaveImportWindow::OnImport, this);
|
||||
row2->Add(ok_button, 0, wxALL, 5);
|
||||
|
||||
auto* cancel_button = new wxButton(this, wxID_ANY, _("Cancel"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
cancel_button->Bind(wxEVT_BUTTON, [this](auto&)
|
||||
{
|
||||
m_return_code = wxCANCEL;
|
||||
Close();
|
||||
});
|
||||
row2->Add(cancel_button, 0, wxALIGN_RIGHT | wxALL, 5);
|
||||
|
||||
sizer->Add(row2, 1, wxEXPAND, 5);
|
||||
}
|
||||
|
||||
this->SetSizerAndFit(sizer);
|
||||
this->Centre(wxBOTH);
|
||||
}
|
||||
|
||||
void SaveImportWindow::EndModal(int retCode)
|
||||
{
|
||||
wxDialog::EndModal(retCode);
|
||||
SetReturnCode(m_return_code);
|
||||
}
|
||||
|
||||
void SaveImportWindow::OnImport(wxCommandEvent& event)
|
||||
{
|
||||
const auto source_path = m_source_selection->GetPath();
|
||||
if (source_path.empty())
|
||||
return;
|
||||
|
||||
// try to find cemu_meta file to verify targetid
|
||||
const std::string zipfile = source_path.ToUTF8().data();
|
||||
|
||||
int ziperr;
|
||||
auto* zip = zip_open(zipfile.c_str(), ZIP_RDONLY, &ziperr);
|
||||
if (zip)
|
||||
{
|
||||
const sint32 numEntries = zip_get_num_entries(zip, 0);
|
||||
for (sint32 i = 0; i < numEntries; i++)
|
||||
{
|
||||
zip_stat_t sb{};
|
||||
if (zip_stat_index(zip, i, 0, &sb) != 0)
|
||||
continue;
|
||||
|
||||
if (!boost::equals(sb.name, "cemu_meta"))
|
||||
continue;
|
||||
|
||||
auto* zip_file = zip_fopen_index(zip, i, 0);
|
||||
if (zip_file == nullptr)
|
||||
continue;
|
||||
|
||||
auto buffer = std::make_unique<char[]>(sb.size);
|
||||
if (zip_fread(zip_file, buffer.get(), sb.size) == sb.size)
|
||||
{
|
||||
std::string_view str{ buffer.get(), sb.size };
|
||||
// titleId = {:#016x}
|
||||
if(boost::starts_with(str, "titleId = "))
|
||||
{
|
||||
const uint64_t titleId = ConvertString<uint64>(str.substr(sizeof("titleId = ") + 1), 16);
|
||||
if(titleId != 0 && titleId != m_title_id)
|
||||
{
|
||||
const auto msg = wxStringFormat2(_("You are trying to import a savegame for a different title than your currently selected one: {:016x} vs {:016x}\nAre you sure that you want to continue?"), titleId, m_title_id);
|
||||
const auto res = wxMessageBox(msg, _("Error"), wxYES_NO | wxCENTRE | wxICON_WARNING, this);
|
||||
if(res == wxNO)
|
||||
{
|
||||
m_return_code = wxCANCEL;
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zip_fclose(zip_file);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
zip_close(zip);
|
||||
}
|
||||
|
||||
// unzip to tmp folder -> using target path directly now
|
||||
std::error_code ec;
|
||||
//auto tmp_source = fs::temp_directory_path(ec);
|
||||
//if(ec)
|
||||
//{
|
||||
// const auto error_msg = wxStringFormat2(_("Error when getting the temp directory path:\n{}"), GetSystemErrorMessage(ec));
|
||||
// wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
// return;
|
||||
//}
|
||||
|
||||
uint32 target_id = 0;
|
||||
const auto selection = m_target_selection->GetCurrentSelection();
|
||||
if (selection != wxNOT_FOUND)
|
||||
target_id = (uint32)(uintptr_t)m_target_selection->GetClientData(selection);
|
||||
|
||||
if (target_id == 0)
|
||||
{
|
||||
target_id = ConvertString<uint32>(m_target_selection->GetValue().ToStdString(), 16);
|
||||
if (target_id < Account::kMinPersistendId)
|
||||
{
|
||||
const auto msg = wxStringFormat2(_("The given account id is not valid!\nIt must be a hex number bigger or equal than {:08x}"), Account::kMinPersistendId);
|
||||
wxMessageBox(msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fs::path target_path = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/{:08x}", (uint32)(m_title_id >> 32), (uint32)(m_title_id & 0xFFFFFFFF), target_id);
|
||||
if (fs::exists(target_path))
|
||||
{
|
||||
if (!fs::is_directory(target_path))
|
||||
{
|
||||
const auto msg = wxStringFormat2(_("There's already a file at the target directory:\n{}"), _utf8Wrapper(target_path));
|
||||
wxMessageBox(msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
m_return_code = wxCANCEL;
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto msg = _("There's already a save game available for the target account, do you want to overwrite it?\nThis will delete the existing save files for the account and replace them.");
|
||||
const auto result = wxMessageBox(msg, _("Error"), wxYES_NO | wxCENTRE | wxICON_WARNING, this);
|
||||
if (result == wxNO)
|
||||
{
|
||||
m_return_code = wxCANCEL;
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
while (fs::exists(target_path, ec) || ec)
|
||||
{
|
||||
fs::remove_all(target_path, ec);
|
||||
|
||||
if (ec)
|
||||
{
|
||||
const auto error_msg = wxStringFormat2(_("Error when trying to delete the former save game:\n{}"), GetSystemErrorMessage(ec));
|
||||
wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this);
|
||||
return;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
}
|
||||
|
||||
// extract zip file
|
||||
|
||||
//tmp_source.append("Cemu").append(fmt::format("{}", rand()));
|
||||
|
||||
//tmp_source = getMlcPath(L"usr/save");
|
||||
//tmp_source /= fmt::format("{:08x}/{:08x}/user/{:08x}_tmp", (uint32)(m_title_id >> 32), (uint32)(m_title_id & 0xFFFFFFFF), target_id);
|
||||
auto tmp_source = target_path;
|
||||
|
||||
fs::create_directories(tmp_source, ec);
|
||||
if (ec)
|
||||
{
|
||||
const auto error_msg = wxStringFormat2(_("Error when creating the extraction path:\n{}"), GetSystemErrorMessage(ec));
|
||||
wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
return;
|
||||
}
|
||||
|
||||
zip = zip_open(zipfile.c_str(), ZIP_RDONLY, &ziperr);
|
||||
if (!zip)
|
||||
{
|
||||
const auto error_msg = wxStringFormat2(_("Error when opening the import zip file:\n{}"), GetSystemErrorMessage(ec));
|
||||
wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
return;
|
||||
}
|
||||
|
||||
sint32 numEntries = zip_get_num_entries(zip, 0);
|
||||
for (sint32 i = 0; i < numEntries; i++)
|
||||
{
|
||||
zip_stat_t sb{};
|
||||
if (zip_stat_index(zip, i, 0, &sb) != 0)
|
||||
{
|
||||
forceLog_printf("zip stat index failed on %d entry", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (boost::starts_with(sb.name, "../") || boost::starts_with(sb.name, "..\\"))
|
||||
continue; // bad path
|
||||
|
||||
if (boost::equals(sb.name, "cemu_meta"))
|
||||
continue;
|
||||
|
||||
size_t sb_name_len = strlen(sb.name);
|
||||
if (sb_name_len == 0)
|
||||
continue;
|
||||
|
||||
const auto path = fs::path(tmp_source).append(sb.name);
|
||||
if (sb.name[sb_name_len - 1] == '/')
|
||||
{
|
||||
fs::create_directories(path, ec);
|
||||
if (ec)
|
||||
forceLog_printf("can't create directory %s: %s", sb.name, ec.message().c_str());
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sb.size == 0)
|
||||
continue;
|
||||
|
||||
if (sb.size > (1024 * 1024 * 128))
|
||||
continue; // skip unusually huge files
|
||||
|
||||
auto* zip_file = zip_fopen_index(zip, i, 0);
|
||||
if (zip_file == nullptr)
|
||||
continue;
|
||||
|
||||
auto buffer = std::make_unique<char[]>(sb.size);
|
||||
if (zip_fread(zip_file, buffer.get(), sb.size) == sb.size)
|
||||
{
|
||||
std::ofstream file(path, std::ios::out | std::ios::binary);
|
||||
if (file.is_open())
|
||||
file.write(buffer.get(), sb.size);
|
||||
}
|
||||
|
||||
zip_fclose(zip_file);
|
||||
}
|
||||
|
||||
zip_close(zip);
|
||||
// extracted all files to tmp_source
|
||||
|
||||
// edit meta saveinfo.xml
|
||||
fs::path saveinfo = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/saveinfo.xml", (uint32)(m_title_id >> 32), (uint32)(m_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)
|
||||
{
|
||||
// delete old node if available
|
||||
auto old_persistend_id_string = fmt::format("{:08x}", target_id);
|
||||
const auto node_entry = info_node.find_child([&old_persistend_id_string](const pugi::xml_node& node)
|
||||
{
|
||||
return boost::iequals(node.attribute("persistentId").as_string(), old_persistend_id_string);
|
||||
});
|
||||
// add save-entry node if not available yet
|
||||
if (!node_entry)
|
||||
{
|
||||
// add new node
|
||||
auto new_persistend_id_string = fmt::format("{:08x}", target_id);
|
||||
auto new_node = info_node.append_child("account");
|
||||
new_node.append_attribute("persistentId").set_value(new_persistend_id_string.c_str());
|
||||
auto timestamp = new_node.append_child("timestamp");
|
||||
timestamp.text().set(fmt::format("{:016x}", coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK).c_str()); // TODO time not initialized yet?
|
||||
|
||||
if(!doc.save_file(saveinfo.c_str()))
|
||||
forceLog_printf("couldn't insert save entry in saveinfo.xml: %s", saveinfo.generic_u8string().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
} // todo create saveinfo if doesnt exist
|
||||
|
||||
|
||||
/*wxMessageBox(fmt::format("{}\n{}", tmp_source.generic_u8string(), target_path.generic_u8string()), _("Error"), wxOK | wxCENTRE, this);
|
||||
|
||||
fs::rename(tmp_source, target_path, ec);
|
||||
if (ec)
|
||||
{
|
||||
const auto error_msg = wxStringFormat2(_("Error when trying to move the extracted save game:\n{}"), GetSystemErrorMessage(ec));
|
||||
wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this);
|
||||
return;
|
||||
}*/
|
||||
|
||||
m_target_id = target_id;
|
||||
m_return_code = wxOK;
|
||||
Close();
|
||||
}
|
||||
25
src/gui/dialogs/SaveImport/SaveImportWindow.h
Normal file
25
src/gui/dialogs/SaveImport/SaveImportWindow.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include <wx/dialog.h>
|
||||
|
||||
class SaveImportWindow : public wxDialog
|
||||
{
|
||||
public:
|
||||
SaveImportWindow(wxWindow* parent, uint64 title_id);
|
||||
|
||||
void EndModal(int retCode) override;
|
||||
|
||||
uint32 GetTargetPersistentId() const { return m_target_id; }
|
||||
private:
|
||||
void OnImport(wxCommandEvent& event);
|
||||
|
||||
class wxFilePickerCtrl* m_source_selection;
|
||||
class wxComboBox* m_target_selection;
|
||||
|
||||
uint32 m_target_id = 0;
|
||||
const uint64 m_title_id;
|
||||
const fs::path m_source_file;
|
||||
int m_return_code = wxCANCEL;
|
||||
};
|
||||
|
||||
|
||||
198
src/gui/dialogs/SaveImport/SaveTransfer.cpp
Normal file
198
src/gui/dialogs/SaveImport/SaveTransfer.cpp
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
#include "SaveTransfer.h"
|
||||
|
||||
#include "pugixml.hpp"
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/choice.h>
|
||||
#include <wx/combobox.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/frame.h>
|
||||
#include <wx/msgdlg.h>
|
||||
|
||||
#include "Cafe/Account/Account.h"
|
||||
#include "config/ActiveSettings.h"
|
||||
#include "util/helpers/helpers.h"
|
||||
#include "gui/helpers/wxHelpers.h"
|
||||
|
||||
SaveTransfer::SaveTransfer(wxWindow* parent, uint64 title_id, const wxString& source_account, uint32 source_id)
|
||||
: wxDialog(parent, wxID_ANY, _("Save transfer"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxFRAME_TOOL_WINDOW | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX),
|
||||
m_title_id(title_id), m_source_id(source_id)
|
||||
{
|
||||
auto* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
{
|
||||
auto* row1 = new wxFlexGridSizer(0, 2, 0, 0);
|
||||
row1->AddGrowableCol(1);
|
||||
|
||||
row1->Add(new wxStaticText(this, wxID_ANY, _("Source")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
auto* source_choice = new wxChoice(this, wxID_ANY);
|
||||
source_choice->SetMinSize({170, -1});
|
||||
source_choice->Append(source_account);
|
||||
source_choice->SetSelection(0);
|
||||
row1->Add(source_choice, 1, wxALL | wxEXPAND, 5);
|
||||
|
||||
row1->Add(new wxStaticText(this, wxID_ANY, _("Target")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
|
||||
const auto& accounts = Account::GetAccounts();
|
||||
m_target_selection = new wxComboBox(this, wxID_ANY);
|
||||
for (const auto& account : accounts)
|
||||
{
|
||||
if (account.GetPersistentId() == m_source_id)
|
||||
continue;
|
||||
|
||||
m_target_selection->Append(fmt::format("{:x} ({})", account.GetPersistentId(),
|
||||
boost::nowide::narrow(account.GetMiiName().data(), account.GetMiiName().size())),
|
||||
(void*)(uintptr_t)account.GetPersistentId());
|
||||
}
|
||||
row1->Add(m_target_selection, 1, wxALL | wxEXPAND, 5);
|
||||
|
||||
sizer->Add(row1, 0, wxEXPAND, 5);
|
||||
}
|
||||
|
||||
{
|
||||
auto* row2 = new wxFlexGridSizer(0, 2, 0, 0);
|
||||
row2->AddGrowableCol(1);
|
||||
row2->SetFlexibleDirection(wxBOTH);
|
||||
row2->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
||||
|
||||
auto* ok_button = new wxButton(this, wxID_ANY, _("OK"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
ok_button->Bind(wxEVT_BUTTON, &SaveTransfer::OnTransfer, this);
|
||||
row2->Add(ok_button, 0, wxALL, 5);
|
||||
|
||||
auto* cancel_button = new wxButton(this, wxID_ANY, _("Cancel"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
cancel_button->Bind(wxEVT_BUTTON, [this](auto&)
|
||||
{
|
||||
m_return_code = wxCANCEL;
|
||||
Close();
|
||||
});
|
||||
row2->Add(cancel_button, 0, wxALIGN_RIGHT | wxALL, 5);
|
||||
|
||||
sizer->Add(row2, 1, wxEXPAND, 5);
|
||||
}
|
||||
|
||||
this->SetSizerAndFit(sizer);
|
||||
this->Centre(wxBOTH);
|
||||
}
|
||||
|
||||
void SaveTransfer::EndModal(int retCode)
|
||||
{
|
||||
wxDialog::EndModal(retCode);
|
||||
SetReturnCode(m_return_code);
|
||||
}
|
||||
|
||||
void SaveTransfer::OnTransfer(wxCommandEvent& event)
|
||||
{
|
||||
uint32 target_id = 0;
|
||||
const auto selection = m_target_selection->GetCurrentSelection();
|
||||
if(selection != wxNOT_FOUND)
|
||||
target_id = (uint32)(uintptr_t)m_target_selection->GetClientData(selection);
|
||||
|
||||
if (target_id == 0)
|
||||
{
|
||||
target_id = ConvertString<uint32>(m_target_selection->GetValue().ToStdString(), 16);
|
||||
if(target_id < Account::kMinPersistendId)
|
||||
{
|
||||
const auto msg = wxStringFormat2(_("The given account id is not valid!\nIt must be a hex number bigger or equal than {:08x}"), Account::kMinPersistendId);
|
||||
wxMessageBox(msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const fs::path source_path = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/{:08x}", (uint32)(m_title_id >> 32), (uint32)(m_title_id & 0xFFFFFFFF), m_source_id);
|
||||
if (!fs::exists(source_path) || !fs::is_directory(source_path))
|
||||
return;
|
||||
|
||||
const fs::path target_path = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/{:08x}", (uint32)(m_title_id >> 32), (uint32)(m_title_id & 0xFFFFFFFF), target_id);
|
||||
if (fs::exists(target_path))
|
||||
{
|
||||
if(!fs::is_directory(target_path))
|
||||
{
|
||||
const auto msg = wxStringFormat2(_("There's already a file at the target directory:\n{}"), _utf8Wrapper(target_path));
|
||||
wxMessageBox(msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
m_return_code = wxCANCEL;
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto msg = _("There's already a save game available for the target account, do you want to overwrite it?\nThis will delete the existing save files for the account and replace them.");
|
||||
const auto result = wxMessageBox(msg, _("Error"), wxYES_NO | wxCENTRE | wxICON_WARNING, this);
|
||||
if(result == wxNO)
|
||||
{
|
||||
m_return_code = wxCANCEL;
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
while (fs::exists(target_path, ec) || ec)
|
||||
{
|
||||
fs::remove_all(target_path, ec);
|
||||
|
||||
if (ec)
|
||||
{
|
||||
const auto error_msg = wxStringFormat2(_("Error when trying to delete the former save game:\n{}"), GetSystemErrorMessage(ec));
|
||||
wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this);
|
||||
return;
|
||||
}
|
||||
|
||||
// wait so filesystem doesnt
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
}
|
||||
|
||||
// edit meta saveinfo.xml
|
||||
bool meta_file_edited = false;
|
||||
const fs::path saveinfo = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/saveinfo.xml", (uint32)(m_title_id >> 32), (uint32)(m_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)
|
||||
{
|
||||
// delete old node if available
|
||||
auto old_persistend_id_string = fmt::format("{:08x}", target_id);
|
||||
const auto delete_entry = info_node.find_child([&old_persistend_id_string](const pugi::xml_node& node)
|
||||
{
|
||||
return boost::iequals(node.attribute("persistentId").as_string(), old_persistend_id_string);
|
||||
});
|
||||
if (delete_entry)
|
||||
info_node.remove_child(delete_entry);
|
||||
|
||||
|
||||
// move new node
|
||||
auto new_persistend_id_string = fmt::format("{:08x}", m_source_id);
|
||||
auto move_entry = info_node.find_child([&new_persistend_id_string](const pugi::xml_node& node)
|
||||
{
|
||||
return boost::iequals(node.attribute("persistentId").as_string(), new_persistend_id_string);
|
||||
});
|
||||
if(move_entry)
|
||||
move_entry.attribute("persistentId").set_value(old_persistend_id_string.c_str());
|
||||
else
|
||||
{
|
||||
// TODO: create if not found! (shouldnt happen though)
|
||||
}
|
||||
|
||||
meta_file_edited = doc.save_file(saveinfo.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!meta_file_edited)
|
||||
forceLog_printf("SaveTransfer::OnTransfer: couldn't update save entry in saveinfo.xml: %s", saveinfo.generic_u8string().c_str());
|
||||
|
||||
std::error_code ec;
|
||||
fs::rename(source_path, target_path, ec);
|
||||
if (ec)
|
||||
{
|
||||
const auto error_msg = wxStringFormat2(_("Error when trying to move the save game:\n{}"), GetSystemErrorMessage(ec));
|
||||
wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this);
|
||||
return;
|
||||
}
|
||||
|
||||
m_target_id = target_id;
|
||||
m_return_code = wxOK;
|
||||
Close();
|
||||
}
|
||||
24
src/gui/dialogs/SaveImport/SaveTransfer.h
Normal file
24
src/gui/dialogs/SaveImport/SaveTransfer.h
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include <wx/dialog.h>
|
||||
|
||||
class wxComboBox;
|
||||
|
||||
class SaveTransfer : public wxDialog
|
||||
{
|
||||
public:
|
||||
SaveTransfer(wxWindow* parent, uint64 title_id, const wxString& source_account, uint32 source_id);
|
||||
|
||||
void EndModal(int retCode) override;
|
||||
|
||||
uint32 GetTargetPersistentId() const { return m_target_id; }
|
||||
private:
|
||||
void OnTransfer(wxCommandEvent& event);
|
||||
|
||||
wxComboBox* m_target_selection;
|
||||
|
||||
uint32 m_target_id = 0;
|
||||
const uint64 m_title_id;
|
||||
const uint32 m_source_id;
|
||||
int m_return_code = wxCANCEL;
|
||||
};
|
||||
299
src/gui/guiWrapper.cpp
Normal file
299
src/gui/guiWrapper.cpp
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
#include "gui/wxgui.h"
|
||||
#include "gui/guiWrapper.h"
|
||||
#include "gui/CemuApp.h"
|
||||
#include "gui/MainWindow.h"
|
||||
#include "gui/debugger/DebuggerWindow2.h"
|
||||
#include "Cafe/HW/Latte/Core/Latte.h"
|
||||
#include "config/ActiveSettings.h"
|
||||
#include "config/CemuConfig.h"
|
||||
#include "Cafe/HW/Latte/Renderer/Renderer.h"
|
||||
#include "Cafe/CafeSystem.h"
|
||||
|
||||
#include "wxHelper.h"
|
||||
|
||||
WindowInfo g_window_info {};
|
||||
|
||||
std::shared_mutex g_mutex;
|
||||
MainWindow* g_mainFrame = nullptr;
|
||||
|
||||
#if BOOST_OS_WINDOWS
|
||||
void wxMatchCemuhookEventIds();
|
||||
|
||||
void _wxLaunch()
|
||||
{
|
||||
SetThreadName("MainThread_UI");
|
||||
wxEntry();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void gui_create()
|
||||
{
|
||||
SetThreadName("MainThread");
|
||||
#if BOOST_OS_WINDOWS
|
||||
wxMatchCemuhookEventIds();
|
||||
// on Windows wxWidgets there is a bug where wxDirDialog->ShowModal will deadlock in Windows internals somehow
|
||||
// moving the UI thread off the main thread fixes this
|
||||
std::thread t = std::thread(_wxLaunch);
|
||||
t.join();
|
||||
#else
|
||||
int argc = 0;
|
||||
char* argv[1]{};
|
||||
wxEntry(argc, argv);
|
||||
#endif
|
||||
}
|
||||
|
||||
WindowInfo& gui_getWindowInfo()
|
||||
{
|
||||
return g_window_info;
|
||||
}
|
||||
|
||||
void gui_updateWindowTitles(bool isIdle, bool isLoading, double fps)
|
||||
{
|
||||
std::string windowText;
|
||||
windowText = fmt::format("" EMULATOR_NAME " {}.{}{}", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_SUFFIX);
|
||||
|
||||
if (isIdle)
|
||||
{
|
||||
if (g_mainFrame)
|
||||
g_mainFrame->AsyncSetTitle(windowText);
|
||||
return;
|
||||
}
|
||||
if (isLoading)
|
||||
{
|
||||
windowText.append(" - loading...");
|
||||
if (g_mainFrame)
|
||||
g_mainFrame->AsyncSetTitle(windowText);
|
||||
return;
|
||||
}
|
||||
|
||||
const char* renderer = "";
|
||||
if(g_renderer)
|
||||
{
|
||||
switch(g_renderer->GetType())
|
||||
{
|
||||
case RendererAPI::OpenGL:
|
||||
renderer = "[OpenGL]";
|
||||
break;
|
||||
case RendererAPI::Vulkan:
|
||||
renderer = "[Vulkan]";
|
||||
break;
|
||||
default: ;
|
||||
}
|
||||
}
|
||||
|
||||
// get GPU vendor/mode
|
||||
const char* graphicMode = "[Generic]";
|
||||
if (LatteGPUState.glVendor == GLVENDOR_AMD)
|
||||
graphicMode = "[AMD GPU]";
|
||||
else if (LatteGPUState.glVendor == GLVENDOR_INTEL_LEGACY)
|
||||
graphicMode = "[Intel GPU - Legacy]";
|
||||
else if (LatteGPUState.glVendor == GLVENDOR_INTEL_NOLEGACY)
|
||||
graphicMode = "[Intel GPU]";
|
||||
else if (LatteGPUState.glVendor == GLVENDOR_INTEL)
|
||||
graphicMode = "[Intel GPU]";
|
||||
else if (LatteGPUState.glVendor == GLVENDOR_NVIDIA)
|
||||
graphicMode = "[NVIDIA GPU]";
|
||||
|
||||
const uint64 titleId = CafeSystem::GetForegroundTitleId();
|
||||
windowText.append(fmt::format(" - FPS: {:.2f} {} {} [TitleId: {:08x}-{:08x}]", (double)fps, renderer, graphicMode, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)));
|
||||
|
||||
if (ActiveSettings::IsOnlineEnabled())
|
||||
windowText.append(" [Online]");
|
||||
|
||||
windowText.append(" ");
|
||||
windowText.append(CafeSystem::GetForegroundTitleName());
|
||||
// append region
|
||||
CafeConsoleRegion region = CafeSystem::GetForegroundTitleRegion();
|
||||
uint16 titleVersion = CafeSystem::GetForegroundTitleVersion();
|
||||
if (region == CafeConsoleRegion::JPN)
|
||||
windowText.append(fmt::format(" [JP v{}]", titleVersion));
|
||||
else if (region == CafeConsoleRegion::USA)
|
||||
windowText.append(fmt::format(" [US v{}]", titleVersion));
|
||||
else if (region == CafeConsoleRegion::EUR)
|
||||
windowText.append(fmt::format(" [EU v{}]", titleVersion));
|
||||
else
|
||||
windowText.append(fmt::format(" [v{}]", titleVersion));
|
||||
|
||||
std::shared_lock lock(g_mutex);
|
||||
if (g_mainFrame)
|
||||
{
|
||||
g_mainFrame->AsyncSetTitle(windowText);
|
||||
auto* pad = g_mainFrame->GetPadView();
|
||||
if (pad)
|
||||
pad->AsyncSetTitle(fmt::format("GamePad View - FPS: {:.02f}", fps));
|
||||
}
|
||||
}
|
||||
|
||||
void gui_getWindowSize(int* w, int* h)
|
||||
{
|
||||
*w = g_window_info.width;
|
||||
*h = g_window_info.height;
|
||||
}
|
||||
|
||||
void gui_getPadWindowSize(int* w, int* h)
|
||||
{
|
||||
if (g_window_info.pad_open)
|
||||
{
|
||||
*w = g_window_info.pad_width;
|
||||
*h = g_window_info.pad_height;
|
||||
}
|
||||
else
|
||||
{
|
||||
*w = 0;
|
||||
*h = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool gui_isPadWindowOpen()
|
||||
{
|
||||
return g_window_info.pad_open;
|
||||
}
|
||||
|
||||
#if BOOST_OS_LINUX
|
||||
#include <wx/nativewin.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
typedef void GdkDisplay;
|
||||
#endif
|
||||
|
||||
void gui_initHandleContextFromWxWidgetsWindow(WindowHandleInfo& handleInfoOut, class wxWindow* wxw)
|
||||
{
|
||||
#if BOOST_OS_WINDOWS > 0
|
||||
handleInfoOut.hwnd = wxw->GetHWND();
|
||||
#else
|
||||
/* dynamically retrieve GTK imports so we dont have to include and link the whole lib */
|
||||
void (*dyn_gtk_widget_realize)(GtkWidget *widget);
|
||||
dyn_gtk_widget_realize = (void(*)(GtkWidget* widget))dlsym(RTLD_NEXT, "gtk_widget_realize");
|
||||
|
||||
GdkWindow* (*dyn_gtk_widget_get_window)(GtkWidget *widget);
|
||||
dyn_gtk_widget_get_window = (GdkWindow*(*)(GtkWidget* widget))dlsym(RTLD_NEXT, "gtk_widget_get_window");
|
||||
|
||||
GdkDisplay* (*dyn_gdk_window_get_display)(GdkWindow *widget);
|
||||
dyn_gdk_window_get_display = (GdkDisplay*(*)(GdkWindow* window))dlsym(RTLD_NEXT, "gdk_window_get_display");
|
||||
|
||||
Display* (*dyn_gdk_x11_display_get_xdisplay)(GdkDisplay *display);
|
||||
dyn_gdk_x11_display_get_xdisplay = (Display*(*)(GdkDisplay* display))dlsym(RTLD_NEXT, "gdk_x11_display_get_xdisplay");
|
||||
|
||||
Window (*dyn_gdk_x11_window_get_xid)(GdkWindow *window);
|
||||
dyn_gdk_x11_window_get_xid = (Window(*)(GdkWindow *window))dlsym(RTLD_NEXT, "gdk_x11_window_get_xid");
|
||||
|
||||
if(!dyn_gtk_widget_realize || !dyn_gtk_widget_get_window ||
|
||||
!dyn_gdk_window_get_display || !dyn_gdk_x11_display_get_xdisplay ||
|
||||
!dyn_gdk_x11_window_get_xid)
|
||||
{
|
||||
cemuLog_log(LogType::Force, "Unable to load GDK symbols");
|
||||
return;
|
||||
}
|
||||
|
||||
/* end of imports */
|
||||
|
||||
// get window
|
||||
GtkWidget* gtkWidget = (GtkWidget*)wxw->GetHandle(); // returns GtkWidget
|
||||
dyn_gtk_widget_realize(gtkWidget);
|
||||
GdkWindow* gdkWindow = dyn_gtk_widget_get_window(gtkWidget);
|
||||
handleInfoOut.xlib_window = dyn_gdk_x11_window_get_xid(gdkWindow);
|
||||
|
||||
// get display
|
||||
GdkDisplay* gdkDisplay = dyn_gdk_window_get_display(gdkWindow);
|
||||
handleInfoOut.xlib_display = dyn_gdk_x11_display_get_xdisplay(gdkDisplay);
|
||||
if(!handleInfoOut.xlib_display)
|
||||
{
|
||||
cemuLog_log(LogType::Force, "Unable to get xlib display");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool gui_isKeyDown(int key)
|
||||
{
|
||||
if (key >= 256)
|
||||
return false;
|
||||
|
||||
return g_window_info.keydown[key];
|
||||
}
|
||||
|
||||
void gui_notifyGameLoaded()
|
||||
{
|
||||
std::shared_lock lock(g_mutex);
|
||||
if (g_mainFrame)
|
||||
{
|
||||
g_mainFrame->OnGameLoaded();
|
||||
g_mainFrame->UpdateSettingsAfterGameLaunch();
|
||||
}
|
||||
}
|
||||
|
||||
void gui_notifyGameExited()
|
||||
{
|
||||
std::shared_lock lock(g_mutex);
|
||||
if(g_mainFrame)
|
||||
g_mainFrame->RestoreSettingsAfterGameExited();
|
||||
}
|
||||
|
||||
bool gui_isFullScreen()
|
||||
{
|
||||
return g_window_info.is_fullscreen;
|
||||
}
|
||||
|
||||
bool gui_hasScreenshotRequest()
|
||||
{
|
||||
const bool result = g_window_info.has_screenshot_request;
|
||||
g_window_info.has_screenshot_request = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
extern DebuggerWindow2* g_debugger_window;
|
||||
void debuggerWindow_updateViewThreadsafe2()
|
||||
{
|
||||
if (g_debugger_window)
|
||||
{
|
||||
auto* evt = new wxCommandEvent(wxEVT_UPDATE_VIEW);
|
||||
wxQueueEvent(g_debugger_window, evt);
|
||||
}
|
||||
}
|
||||
|
||||
void debuggerWindow_moveIP()
|
||||
{
|
||||
if (g_debugger_window)
|
||||
{
|
||||
auto* evt = new wxCommandEvent(wxEVT_MOVE_IP);
|
||||
wxQueueEvent(g_debugger_window, evt);
|
||||
}
|
||||
}
|
||||
|
||||
void debuggerWindow_notifyDebugBreakpointHit2()
|
||||
{
|
||||
if (g_debugger_window)
|
||||
{
|
||||
auto* evt = new wxCommandEvent(wxEVT_BREAKPOINT_HIT);
|
||||
wxQueueEvent(g_debugger_window, evt);
|
||||
}
|
||||
}
|
||||
|
||||
void debuggerWindow_notifyRun()
|
||||
{
|
||||
if (g_debugger_window)
|
||||
{
|
||||
auto* evt = new wxCommandEvent(wxEVT_RUN);
|
||||
wxQueueEvent(g_debugger_window, evt);
|
||||
}
|
||||
}
|
||||
|
||||
void debuggerWindow_notifyModuleLoaded(void* module)
|
||||
{
|
||||
if (g_debugger_window)
|
||||
{
|
||||
auto* evt = new wxCommandEvent(wxEVT_NOTIFY_MODULE_LOADED);
|
||||
evt->SetClientData(module);
|
||||
wxQueueEvent(g_debugger_window, evt);
|
||||
}
|
||||
}
|
||||
|
||||
void debuggerWindow_notifyModuleUnloaded(void* module)
|
||||
{
|
||||
if (g_debugger_window)
|
||||
{
|
||||
auto* evt = new wxCommandEvent(wxEVT_NOTIFY_MODULE_UNLOADED);
|
||||
evt->SetClientData(module);
|
||||
wxQueueEvent(g_debugger_window, evt);
|
||||
}
|
||||
}
|
||||
82
src/gui/guiWrapper.h
Normal file
82
src/gui/guiWrapper.h
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#if BOOST_OS_LINUX > 0
|
||||
#include "xcb/xproto.h"
|
||||
#endif
|
||||
|
||||
struct WindowHandleInfo
|
||||
{
|
||||
#if BOOST_OS_WINDOWS
|
||||
std::atomic<HWND> hwnd;
|
||||
#elif BOOST_OS_LINUX
|
||||
// XLIB
|
||||
Display* xlib_display{};
|
||||
Window xlib_window{};
|
||||
|
||||
// XCB (not used by GTK so we cant retrieve these without making our own window)
|
||||
//xcb_connection_t* xcb_con{};
|
||||
//xcb_window_t xcb_window{};
|
||||
// Wayland
|
||||
// todo
|
||||
#endif
|
||||
};
|
||||
|
||||
struct WindowInfo
|
||||
{
|
||||
std::atomic_bool app_active; // our app is active/has focus
|
||||
|
||||
std::atomic_int32_t width, height; // client size of main window
|
||||
|
||||
std::atomic_bool pad_open; // if separate pad view is open
|
||||
std::atomic_int32_t pad_width, pad_height; // client size of pad window
|
||||
|
||||
std::atomic_bool pad_maximized = false;
|
||||
std::atomic_int32_t restored_pad_x = -1, restored_pad_y = -1;
|
||||
std::atomic_int32_t restored_pad_width = -1, restored_pad_height = -1;
|
||||
|
||||
std::atomic_bool has_screenshot_request;
|
||||
std::atomic_bool is_fullscreen;
|
||||
|
||||
std::atomic_bool keydown[256]{};
|
||||
|
||||
WindowHandleInfo window_main;
|
||||
WindowHandleInfo window_pad;
|
||||
|
||||
// canvas
|
||||
WindowHandleInfo canvas_main;
|
||||
WindowHandleInfo canvas_pad;
|
||||
};
|
||||
|
||||
void gui_create();
|
||||
|
||||
WindowInfo& gui_getWindowInfo();
|
||||
|
||||
void gui_updateWindowTitles(bool isIdle, bool isLoading, double fps);
|
||||
void gui_getWindowSize(int* w, int* h);
|
||||
void gui_getPadWindowSize(int* w, int* h);
|
||||
bool gui_isPadWindowOpen();
|
||||
bool gui_isKeyDown(int key);
|
||||
|
||||
void gui_notifyGameLoaded();
|
||||
void gui_notifyGameExited();
|
||||
|
||||
bool gui_isFullScreen();
|
||||
|
||||
void gui_initHandleContextFromWxWidgetsWindow(WindowHandleInfo& handleInfoOut, class wxWindow* wxw);
|
||||
|
||||
/*
|
||||
* Returns true if a screenshot request is queued
|
||||
* Once this function has returned true, it will reset back to
|
||||
* false until the next time a screenshot is requested
|
||||
*/
|
||||
bool gui_hasScreenshotRequest();
|
||||
|
||||
// debugger stuff
|
||||
void debuggerWindow_updateViewThreadsafe2();
|
||||
void debuggerWindow_notifyDebugBreakpointHit2();
|
||||
void debuggerWindow_notifyRun();
|
||||
void debuggerWindow_moveIP();
|
||||
void debuggerWindow_notifyModuleLoaded(void* module);
|
||||
void debuggerWindow_notifyModuleUnloaded(void* module);
|
||||
44
src/gui/helpers/wxControlObject.h
Normal file
44
src/gui/helpers/wxControlObject.h
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
#include <wx/control.h>
|
||||
|
||||
class wxControlObject : public wxObject
|
||||
{
|
||||
public:
|
||||
wxControlObject(wxControl* control)
|
||||
: m_control(control) {}
|
||||
|
||||
template<typename T>
|
||||
T* GetControl() const
|
||||
{
|
||||
static_assert(std::is_base_of<wxControl, T>::value, "T must inherit from wxControl");
|
||||
return dynamic_cast<T*>(m_control);
|
||||
}
|
||||
|
||||
wxControl* GetControl() const { return m_control; }
|
||||
|
||||
private:
|
||||
wxControl* m_control;
|
||||
};
|
||||
|
||||
class wxControlObjects : public wxObject
|
||||
{
|
||||
public:
|
||||
wxControlObjects(std::vector<wxControl*> controls)
|
||||
: m_controls(std::move(controls)) {}
|
||||
|
||||
template<typename T = wxControl>
|
||||
T* GetControl(int index) const
|
||||
{
|
||||
static_assert(std::is_base_of<wxControl, T>::value, "T must inherit from wxControl");
|
||||
if (index < 0 || index >= m_controls.size())
|
||||
return nullptr;
|
||||
|
||||
return dynamic_cast<T*>(m_controls[index]);
|
||||
}
|
||||
|
||||
const std::vector<wxControl*>& GetControls() const { return m_controls; }
|
||||
|
||||
private:
|
||||
std::vector<wxControl*> m_controls;
|
||||
};
|
||||
49
src/gui/helpers/wxCustomData.h
Normal file
49
src/gui/helpers/wxCustomData.h
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#pragma once
|
||||
|
||||
#include <wx/clntdata.h>
|
||||
|
||||
template <typename T>
|
||||
class wxCustomData : public wxClientData
|
||||
{
|
||||
public:
|
||||
wxCustomData()
|
||||
: m_data({}) {}
|
||||
|
||||
wxCustomData(T data)
|
||||
: m_data(std::move(data)) { }
|
||||
|
||||
const T& GetData() const { return m_data; }
|
||||
|
||||
/// <summary>
|
||||
/// obtains ownership of the data
|
||||
/// </summary>
|
||||
/// <returns>returns the stored data</returns>
|
||||
T get() { return std::move(m_data); }
|
||||
|
||||
/// <summary>
|
||||
/// access to the data without obtaining ownership
|
||||
/// </summary>
|
||||
/// <returns>returns a reference to the stored data</returns>
|
||||
T& ref() { return m_data; }
|
||||
|
||||
bool operator==(const T& o) const { return m_data == o.m_data; }
|
||||
bool operator!=(const T& o) const { return !(*this == o); }
|
||||
|
||||
private:
|
||||
T m_data;
|
||||
};
|
||||
|
||||
//template <typename T>
|
||||
//class wxCustomObject : public wxObject
|
||||
//{
|
||||
//public:
|
||||
// wxCustomObject(T data)
|
||||
// : m_data(std::move(data)) { }
|
||||
//
|
||||
// const T& GetData() const { return m_data; }
|
||||
//
|
||||
// bool operator==(const T& o) const { return m_data == o.m_data; }
|
||||
// bool operator!=(const T& o) const { return !(*this == o); }
|
||||
//private:
|
||||
// T m_data;
|
||||
//};
|
||||
7
src/gui/helpers/wxCustomEvents.cpp
Normal file
7
src/gui/helpers/wxCustomEvents.cpp
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#include "gui/helpers/wxCustomEvents.h"
|
||||
|
||||
wxDEFINE_EVENT(wxEVT_SET_STATUS_BAR_TEXT, wxSetStatusBarTextEvent);
|
||||
wxDEFINE_EVENT(wxEVT_SET_GAUGE_VALUE, wxSetGaugeValue);
|
||||
wxDEFINE_EVENT(wxEVT_REMOVE_ITEM, wxCommandEvent);
|
||||
wxDEFINE_EVENT(wxEVT_SET_TEXT, wxCommandEvent);
|
||||
wxDEFINE_EVENT(wxEVT_ENABLE, wxCommandEvent);
|
||||
74
src/gui/helpers/wxCustomEvents.h
Normal file
74
src/gui/helpers/wxCustomEvents.h
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#pragma once
|
||||
|
||||
#include <wx/event.h>
|
||||
#include <wx/gauge.h>
|
||||
|
||||
class wxStatusBar;
|
||||
class wxControl;
|
||||
|
||||
wxDECLARE_EVENT(wxEVT_REMOVE_ITEM, wxCommandEvent);
|
||||
wxDECLARE_EVENT(wxEVT_SET_TEXT, wxCommandEvent);
|
||||
wxDECLARE_EVENT(wxEVT_ENABLE, wxCommandEvent);
|
||||
|
||||
class wxSetStatusBarTextEvent;
|
||||
wxDECLARE_EVENT(wxEVT_SET_STATUS_BAR_TEXT, wxSetStatusBarTextEvent);
|
||||
class wxSetStatusBarTextEvent : public wxCommandEvent
|
||||
{
|
||||
public:
|
||||
wxSetStatusBarTextEvent(const wxString& text, int number = 0, wxEventType type = wxEVT_SET_STATUS_BAR_TEXT, int id = 0)
|
||||
: wxCommandEvent(type, id), m_text(text), m_number(number) {}
|
||||
|
||||
wxSetStatusBarTextEvent(const wxSetStatusBarTextEvent& event)
|
||||
: wxCommandEvent(event), m_text(event.GetText()), m_number(event.GetNumber()) {}
|
||||
|
||||
|
||||
[[nodiscard]] const wxString& GetText() const { return m_text; }
|
||||
[[nodiscard]] int GetNumber() const { return m_number; }
|
||||
|
||||
wxEvent* Clone() const override
|
||||
{
|
||||
return new wxSetStatusBarTextEvent(*this);
|
||||
}
|
||||
|
||||
private:
|
||||
const wxString m_text;
|
||||
const int m_number;
|
||||
};
|
||||
class wxSetGaugeValue;
|
||||
wxDECLARE_EVENT(wxEVT_SET_GAUGE_VALUE, wxSetGaugeValue);
|
||||
class wxSetGaugeValue : public wxCommandEvent
|
||||
{
|
||||
public:
|
||||
wxSetGaugeValue(int value, wxGauge* gauge, wxControl* text_ctrl = nullptr, const wxString& text = wxEmptyString, wxEventType type = wxEVT_SET_GAUGE_VALUE, int id = 0)
|
||||
: wxCommandEvent(type, id), m_gauge_value(value), m_gauge_range(0), m_text_ctrl(text_ctrl), m_text(text)
|
||||
{
|
||||
SetEventObject(gauge);
|
||||
}
|
||||
|
||||
wxSetGaugeValue(int value, int range, wxGauge* gauge, wxControl* text_ctrl = nullptr, const wxString& text = wxEmptyString, wxEventType type = wxEVT_SET_GAUGE_VALUE, int id = 0)
|
||||
: wxCommandEvent(type, id), m_gauge_value(value), m_gauge_range(range), m_text_ctrl(text_ctrl), m_text(text)
|
||||
{
|
||||
SetEventObject(gauge);
|
||||
}
|
||||
|
||||
wxSetGaugeValue(const wxSetGaugeValue& event)
|
||||
: wxCommandEvent(event), m_gauge_value(event.GetValue()), m_gauge_range(event.GetRange()), m_text_ctrl(event.GetTextCtrl()), m_text(event.GetText())
|
||||
{}
|
||||
|
||||
[[nodiscard]] int GetValue() const { return m_gauge_value; }
|
||||
[[nodiscard]] int GetRange() const { return m_gauge_range; }
|
||||
[[nodiscard]] wxGauge* GetGauge() const { return (wxGauge*)GetEventObject(); }
|
||||
[[nodiscard]] const wxString& GetText() const { return m_text; }
|
||||
[[nodiscard]] wxControl* GetTextCtrl() const { return m_text_ctrl; }
|
||||
|
||||
wxEvent* Clone() const override
|
||||
{
|
||||
return new wxSetGaugeValue(*this);
|
||||
}
|
||||
|
||||
private:
|
||||
const int m_gauge_value, m_gauge_range;
|
||||
wxControl* m_text_ctrl;
|
||||
const wxString m_text;
|
||||
};
|
||||
|
||||
66
src/gui/helpers/wxHelpers.cpp
Normal file
66
src/gui/helpers/wxHelpers.cpp
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
#include "gui/helpers/wxHelpers.h"
|
||||
|
||||
#include <wx/wupdlock.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/slider.h>
|
||||
#include <wx/dirdlg.h>
|
||||
|
||||
#include "gui/helpers/wxControlObject.h"
|
||||
|
||||
void wxAutosizeColumn(wxListCtrlBase* ctrl, int col)
|
||||
{
|
||||
ctrl->SetColumnWidth(col, wxLIST_AUTOSIZE_USEHEADER);
|
||||
int wh = ctrl->GetColumnWidth(col);
|
||||
ctrl->SetColumnWidth(col, wxLIST_AUTOSIZE);
|
||||
int wc = ctrl->GetColumnWidth(col);
|
||||
if (wh > wc)
|
||||
ctrl->SetColumnWidth(col, wxLIST_AUTOSIZE_USEHEADER);
|
||||
}
|
||||
|
||||
void wxAutosizeColumns(wxListCtrlBase* ctrl, int col_start, int col_end)
|
||||
{
|
||||
wxWindowUpdateLocker lock(ctrl);
|
||||
for (int i = col_start; i <= col_end; ++i)
|
||||
wxAutosizeColumn(ctrl, i);
|
||||
}
|
||||
|
||||
void update_slider_text(wxCommandEvent& event, const wxFormatString& format /*= "%d%%"*/)
|
||||
{
|
||||
const auto slider = dynamic_cast<wxSlider*>(event.GetEventObject());
|
||||
wxASSERT(slider);
|
||||
|
||||
auto slider_text = dynamic_cast<wxControlObject*>(event.GetEventUserData())->GetControl<wxStaticText>();
|
||||
wxASSERT(slider_text);
|
||||
|
||||
slider_text->SetLabel(wxString::Format(format, slider->GetValue()));
|
||||
}
|
||||
|
||||
uint32 fix_raw_keycode(uint32 keycode, uint32 raw_flags)
|
||||
{
|
||||
#if BOOST_OS_WINDOWS
|
||||
const auto flags = (HIWORD(raw_flags) & 0xFFF);
|
||||
if(keycode == VK_SHIFT)
|
||||
{
|
||||
if(flags == 0x2A)
|
||||
return 160;
|
||||
else if (flags == 0x36)
|
||||
return 161;
|
||||
}
|
||||
else if (keycode == VK_CONTROL)
|
||||
{
|
||||
if (flags == 0x1d)
|
||||
return 162;
|
||||
else if (flags == 0x11d)
|
||||
return 163;
|
||||
}
|
||||
else if (keycode == VK_MENU)
|
||||
{
|
||||
if ((flags & 0xFF) == 0x38)
|
||||
return 164;
|
||||
else if ((flags & 0xFF) == 0x38)
|
||||
return 165;
|
||||
}
|
||||
#endif
|
||||
|
||||
return keycode;
|
||||
}
|
||||
126
src/gui/helpers/wxHelpers.h
Normal file
126
src/gui/helpers/wxHelpers.h
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
#pragma once
|
||||
|
||||
#include <wx/control.h>
|
||||
#include <wx/listbase.h>
|
||||
#include <wx/string.h>
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<wxString> : formatter<string_view>
|
||||
{
|
||||
template <typename FormatContext>
|
||||
auto format(const wxString& str, FormatContext& ctx)
|
||||
{
|
||||
return formatter<string_view>::format(str.c_str().AsChar(), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
class wxTempEnable
|
||||
{
|
||||
public:
|
||||
wxTempEnable(wxControl* control, bool state = true)
|
||||
: m_control(control), m_state(state)
|
||||
{
|
||||
m_control->Enable(m_state);
|
||||
}
|
||||
|
||||
wxTempEnable(const wxTempEnable&) = delete;
|
||||
wxTempEnable(wxTempEnable&&) noexcept = default;
|
||||
|
||||
~wxTempEnable()
|
||||
{
|
||||
if(m_control)
|
||||
m_control->Enable(!m_state);
|
||||
}
|
||||
|
||||
private:
|
||||
wxControl* m_control;
|
||||
const bool m_state;
|
||||
};
|
||||
|
||||
class wxTempDisable : wxTempEnable
|
||||
{
|
||||
public:
|
||||
wxTempDisable(wxControl* control)
|
||||
: wxTempEnable(control, false) {}
|
||||
};
|
||||
|
||||
template<typename ...TArgs>
|
||||
wxString wxStringFormat2(const wxString& format, TArgs&&...args)
|
||||
{
|
||||
// ignores locale?
|
||||
return fmt::format(format.ToStdString(), std::forward<TArgs>(args)...);
|
||||
}
|
||||
|
||||
template<typename ...TArgs>
|
||||
wxString wxStringFormat2W(const wxString& format, TArgs&&...args)
|
||||
{
|
||||
return fmt::format(format.ToStdWstring(), std::forward<TArgs>(args)...);
|
||||
}
|
||||
|
||||
// executes a function when destroying the obj
|
||||
template<typename TFunction, typename ...TArgs>
|
||||
class wxDTorFunc
|
||||
{
|
||||
public:
|
||||
wxDTorFunc(TFunction&& func, TArgs&&... args)
|
||||
{
|
||||
auto bound = std::bind(std::forward<TFunction>(func), std::forward<TArgs>(args)...);
|
||||
// m_function = [bound] { bound(); };
|
||||
m_function = [bound{ std::move(bound) }]{ bound(); };
|
||||
|
||||
}
|
||||
~wxDTorFunc()
|
||||
{
|
||||
m_function();
|
||||
}
|
||||
private:
|
||||
std::function<void()> m_function;
|
||||
};
|
||||
|
||||
void wxAutosizeColumn(wxListCtrlBase* ctrl, int col);
|
||||
void wxAutosizeColumns(wxListCtrlBase* ctrl, int col_start, int col_end);
|
||||
|
||||
// creates wxString from utf8 string
|
||||
inline wxString to_wxString(std::string_view str)
|
||||
{
|
||||
return wxString::FromUTF8(str.data(), str.size());
|
||||
}
|
||||
|
||||
// creates utf8 std::string from wxString
|
||||
inline std::string from_wxString(const wxString& str)
|
||||
{
|
||||
const auto tmp = str.ToUTF8();
|
||||
return std::string{ tmp.data(), tmp.length() };
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
T get_next_sibling(const T element)
|
||||
{
|
||||
static_assert(std::is_pointer_v<T> && std::is_base_of_v<wxControl, std::remove_pointer_t<T>>, "element must be a pointer and inherit from wxControl");
|
||||
for (auto sibling = element->GetNextSibling(); sibling; sibling = sibling->GetNextSibling())
|
||||
{
|
||||
if (auto result = dynamic_cast<T>(sibling))
|
||||
return result;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T get_prev_sibling(const T element)
|
||||
{
|
||||
static_assert(std::is_pointer_v<T> && std::is_base_of_v<wxControl, std::remove_pointer_t<T>>, "element must be a pointer and inherit from wxControl");
|
||||
for (auto sibling = element->GetPrevSibling(); sibling; sibling = sibling->GetPrevSibling())
|
||||
{
|
||||
auto result = dynamic_cast<T>(sibling);
|
||||
if (result)
|
||||
return result;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void update_slider_text(wxCommandEvent& event, const wxFormatString& format = "%d%%");
|
||||
|
||||
uint32 fix_raw_keycode(uint32 keycode, uint32 raw_flags);
|
||||
32
src/gui/helpers/wxLogEvent.h
Normal file
32
src/gui/helpers/wxLogEvent.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include <wx/event.h>
|
||||
|
||||
class wxLogEvent;
|
||||
wxDECLARE_EVENT(EVT_LOG, wxLogEvent);
|
||||
|
||||
class wxLogEvent : public wxCommandEvent
|
||||
{
|
||||
public:
|
||||
wxLogEvent(const wxString& filter, const wxString& message)
|
||||
: wxCommandEvent(EVT_LOG), m_filter(filter), m_message(message) { }
|
||||
|
||||
wxLogEvent(const wxLogEvent& event)
|
||||
: wxCommandEvent(event), m_filter(event.m_filter), m_message(event.m_message) { }
|
||||
|
||||
wxEvent* Clone() const { return new wxLogEvent(*this); }
|
||||
|
||||
[[nodiscard]] const wxString& GetFilter() const
|
||||
{
|
||||
return m_filter;
|
||||
}
|
||||
|
||||
[[nodiscard]] const wxString& GetMessage() const
|
||||
{
|
||||
return m_message;
|
||||
}
|
||||
|
||||
private:
|
||||
wxString m_filter;
|
||||
wxString m_message;
|
||||
};
|
||||
303
src/gui/input/InputAPIAddWindow.cpp
Normal file
303
src/gui/input/InputAPIAddWindow.cpp
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
#include "gui/input/InputAPIAddWindow.h"
|
||||
|
||||
#include "input/InputManager.h"
|
||||
#include "gui/helpers/wxCustomData.h"
|
||||
#include "gui/helpers/wxHelpers.h"
|
||||
#include "input/api/Controller.h"
|
||||
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/choice.h>
|
||||
#include <wx/statline.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/combobox.h>
|
||||
#include <wx/msgdlg.h>
|
||||
#include <wx/wupdlock.h>
|
||||
|
||||
#include "input/ControllerFactory.h"
|
||||
|
||||
wxDEFINE_EVENT(wxControllersRefreshed, wxCommandEvent);
|
||||
|
||||
using wxTypeData = wxCustomData<InputAPI::Type>;
|
||||
using wxControllerData = wxCustomData<ControllerPtr>;
|
||||
|
||||
InputAPIAddWindow::InputAPIAddWindow(wxWindow* parent, const wxPoint& position,
|
||||
const std::vector<ControllerPtr>& controllers)
|
||||
: wxDialog(parent, wxID_ANY, _("Add input API"), position, wxDefaultSize, 0), m_controllers(controllers)
|
||||
{
|
||||
this->SetSizeHints(wxDefaultSize, wxDefaultSize);
|
||||
|
||||
auto* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
{
|
||||
auto* api_row = new wxFlexGridSizer(2);
|
||||
|
||||
// API
|
||||
api_row->Add(new wxStaticText(this, wxID_ANY, _("API")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
|
||||
m_input_api = new wxChoice(this, wxID_ANY);
|
||||
auto& providers = InputManager::instance().get_api_providers();
|
||||
for (const auto& p : providers)
|
||||
{
|
||||
if (p.empty())
|
||||
continue;
|
||||
|
||||
const auto provider = *p.begin();
|
||||
m_input_api->Append(to_wxString(provider->api_name()), new wxTypeData(provider->api()));
|
||||
}
|
||||
|
||||
m_input_api->Bind(wxEVT_CHOICE, &InputAPIAddWindow::on_api_selected, this);
|
||||
api_row->Add(m_input_api, 1, wxALL | wxEXPAND, 5);
|
||||
|
||||
// Controller
|
||||
api_row->Add(new wxStaticText(this, wxID_ANY, _("Controller")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
|
||||
m_controller_list = new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, nullptr,
|
||||
wxCB_READONLY);
|
||||
m_controller_list->Bind(wxEVT_COMBOBOX_DROPDOWN, &InputAPIAddWindow::on_controller_dropdown, this);
|
||||
m_controller_list->Bind(wxEVT_COMBOBOX, &InputAPIAddWindow::on_controller_selected, this);
|
||||
m_controller_list->SetMinSize(wxSize(240, -1));
|
||||
m_controller_list->Disable();
|
||||
api_row->Add(m_controller_list, 1, wxALL | wxEXPAND, 5);
|
||||
|
||||
sizer->Add(api_row, 0, wxEXPAND, 5);
|
||||
}
|
||||
|
||||
sizer->Add(new wxStaticLine(this), 0, wxEXPAND);
|
||||
|
||||
{
|
||||
auto* end_row = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
m_ok_button = new wxButton(this, wxID_ANY, _("Add"));
|
||||
m_ok_button->Bind(wxEVT_BUTTON, &InputAPIAddWindow::on_add_button, this);
|
||||
m_ok_button->Disable();
|
||||
end_row->Add(m_ok_button, 0, wxALL, 5);
|
||||
|
||||
auto* cancel_button = new wxButton(this, wxID_ANY, _("Cancel"));
|
||||
cancel_button->Bind(wxEVT_BUTTON, &InputAPIAddWindow::on_close_button, this);
|
||||
end_row->Add(cancel_button, 0, wxALL, 5);
|
||||
|
||||
sizer->Add(end_row, 0, wxEXPAND, 5);
|
||||
}
|
||||
|
||||
{
|
||||
// optional settings
|
||||
m_settings_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
auto* panel_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
panel_sizer->Add(new wxStaticLine(m_settings_panel), 0, wxEXPAND, 0);
|
||||
|
||||
{
|
||||
auto* row = new wxBoxSizer(wxHORIZONTAL);
|
||||
// we only have dsu settings atm, so add elements now
|
||||
row->Add(new wxStaticText(m_settings_panel, wxID_ANY, _("IP")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
m_ip = new wxTextCtrl(m_settings_panel, wxID_ANY, wxT("127.0.0.1"));
|
||||
row->Add(m_ip, 0, wxALL, 5);
|
||||
|
||||
row->Add(new wxStaticText(m_settings_panel, wxID_ANY, _("Port")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
m_port = new wxTextCtrl(m_settings_panel, wxID_ANY, wxT("26760"));
|
||||
row->Add(m_port, 0, wxALL, 5);
|
||||
|
||||
panel_sizer->Add(row, 0, wxEXPAND);
|
||||
}
|
||||
|
||||
m_settings_panel->SetSizer(panel_sizer);
|
||||
m_settings_panel->Layout();
|
||||
m_settings_panel->Hide();
|
||||
|
||||
sizer->Add(m_settings_panel, 1, wxEXPAND);
|
||||
}
|
||||
|
||||
this->SetSizer(sizer);
|
||||
this->Layout();
|
||||
sizer->Fit(this);
|
||||
|
||||
this->Bind(wxControllersRefreshed, &InputAPIAddWindow::on_controllers_refreshed, this);
|
||||
}
|
||||
|
||||
void InputAPIAddWindow::on_add_button(wxCommandEvent& event)
|
||||
{
|
||||
const auto selection = m_input_api->GetSelection();
|
||||
if (selection == wxNOT_FOUND)
|
||||
{
|
||||
cemu_assert_debug(false);
|
||||
EndModal(wxID_CANCEL);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& c : m_controllers)
|
||||
{
|
||||
if (*c == *m_controller)
|
||||
{
|
||||
wxMessageBox(_("The controller is already added!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_type = static_cast<wxTypeData*>(m_input_api->GetClientObject(selection))->get();
|
||||
EndModal(wxID_OK);
|
||||
}
|
||||
|
||||
void InputAPIAddWindow::on_close_button(wxCommandEvent& event)
|
||||
{
|
||||
EndModal(wxID_CANCEL);
|
||||
}
|
||||
|
||||
bool InputAPIAddWindow::has_custom_settings() const
|
||||
{
|
||||
const auto selection = m_input_api->GetStringSelection();
|
||||
return selection == to_wxString(to_string(InputAPI::DSUClient));
|
||||
}
|
||||
|
||||
std::unique_ptr<ControllerProviderSettings> InputAPIAddWindow::get_settings() const
|
||||
{
|
||||
if (!has_custom_settings())
|
||||
return {};
|
||||
|
||||
return std::make_unique<DSUProviderSettings>(m_ip->GetValue().ToStdString(),
|
||||
ConvertString<uint16>(m_port->GetValue().ToStdString()));
|
||||
}
|
||||
|
||||
void InputAPIAddWindow::on_api_selected(wxCommandEvent& event)
|
||||
{
|
||||
if (m_input_api->GetSelection() == wxNOT_FOUND)
|
||||
return;
|
||||
|
||||
m_controller_list->Enable();
|
||||
m_controller_list->SetSelection(wxNOT_FOUND);
|
||||
|
||||
const auto selection = m_input_api->GetStringSelection();
|
||||
// keyboard is a special case, as theres only one device supported atm
|
||||
if (selection == to_wxString(to_string(InputAPI::Keyboard)))
|
||||
{
|
||||
const auto controllers = InputManager::instance().get_api_provider(InputAPI::Keyboard)->get_controllers();
|
||||
if (!controllers.empty())
|
||||
{
|
||||
m_controller = controllers[0];
|
||||
m_ok_button->Enable();
|
||||
|
||||
m_controller_list->Clear();
|
||||
const auto display_name = controllers[0]->display_name();
|
||||
const auto index = m_controller_list->Append(display_name, new wxCustomData(controllers[0]));
|
||||
m_controller_list->SetSelection(index);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
#if BOOST_OS_LINUX
|
||||
// We rely on the wxEVT_COMBOBOX_DROPDOWN event to trigger filling the controller list,
|
||||
// but on wxGTK the dropdown button cannot be clicked if the list is empty
|
||||
// so as a quick and dirty workaround we fill the list here
|
||||
wxCommandEvent tmpCmdEvt;
|
||||
on_controller_dropdown(tmpCmdEvt);
|
||||
#endif
|
||||
}
|
||||
|
||||
const auto show_settings = has_custom_settings();
|
||||
// dsu has special settings for ip/port
|
||||
if (show_settings != m_settings_panel->IsShown())
|
||||
{
|
||||
wxWindowUpdateLocker locker(this);
|
||||
m_settings_panel->Show(show_settings);
|
||||
Layout();
|
||||
Fit();
|
||||
}
|
||||
}
|
||||
|
||||
void InputAPIAddWindow::on_controller_dropdown(wxCommandEvent& event)
|
||||
{
|
||||
if (m_search_running)
|
||||
return;
|
||||
|
||||
int selection = m_input_api->GetSelection();
|
||||
if (selection == wxNOT_FOUND)
|
||||
return;
|
||||
|
||||
const auto type = static_cast<wxAPIType*>(m_input_api->GetClientObject(selection))->get();
|
||||
auto settings = get_settings();
|
||||
|
||||
ControllerProviderPtr provider;
|
||||
if (settings)
|
||||
provider = InputManager::instance().get_api_provider(type, *settings);
|
||||
else
|
||||
provider = InputManager::instance().get_api_provider(type);
|
||||
|
||||
if (!provider)
|
||||
return;
|
||||
|
||||
std::string selected_uuid;
|
||||
selection = m_controller_list->GetSelection();
|
||||
if (selection != wxNOT_FOUND)
|
||||
{
|
||||
// TODO selected_uuid
|
||||
}
|
||||
|
||||
m_search_running = true;
|
||||
|
||||
wxWindowUpdateLocker lock(m_controller_list);
|
||||
m_controller_list->Clear();
|
||||
|
||||
m_controller_list->Append(_("Searching for controllers..."), (wxClientData*)nullptr);
|
||||
m_controller_list->SetSelection(wxNOT_FOUND);
|
||||
|
||||
std::thread([this, provider, selected_uuid]()
|
||||
{
|
||||
auto available_controllers = provider->get_controllers();
|
||||
|
||||
wxCommandEvent event(wxControllersRefreshed);
|
||||
event.SetEventObject(m_controller_list);
|
||||
event.SetClientObject(new wxCustomData(std::move(available_controllers)));
|
||||
event.SetInt(provider->api());
|
||||
event.SetString(selected_uuid);
|
||||
wxPostEvent(this, event);
|
||||
|
||||
m_search_running = false;
|
||||
}).detach();
|
||||
}
|
||||
|
||||
void InputAPIAddWindow::on_controller_selected(wxCommandEvent& event)
|
||||
{
|
||||
if (m_search_running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto selection = m_controller_list->GetSelection();
|
||||
if (selection == wxNOT_FOUND)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto* controller = (wxControllerData*)m_controller_list->GetClientObject(selection))
|
||||
{
|
||||
m_controller = controller->ref();
|
||||
m_ok_button->Enable();
|
||||
}
|
||||
}
|
||||
|
||||
void InputAPIAddWindow::on_controllers_refreshed(wxCommandEvent& event)
|
||||
{
|
||||
const auto type = event.GetInt();
|
||||
wxASSERT(0 <= type && type < InputAPI::MAX);
|
||||
|
||||
auto* controllers = dynamic_cast<wxComboBox*>(event.GetEventObject());
|
||||
wxASSERT(controllers);
|
||||
|
||||
const auto available_controllers = static_cast<wxCustomData<std::vector<std::shared_ptr<ControllerBase>>>*>(event.
|
||||
GetClientObject())->get();
|
||||
const auto selected_uuid = event.GetString().ToStdString();
|
||||
bool item_selected = false;
|
||||
|
||||
wxWindowUpdateLocker lock(controllers);
|
||||
controllers->Clear();
|
||||
for (const auto& c : available_controllers)
|
||||
{
|
||||
const auto display_name = c->display_name();
|
||||
const auto uuid = c->uuid();
|
||||
const auto index = controllers->Append(display_name, new wxCustomData(c));
|
||||
if (!item_selected && selected_uuid == uuid)
|
||||
{
|
||||
controllers->SetSelection(index);
|
||||
item_selected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
53
src/gui/input/InputAPIAddWindow.h
Normal file
53
src/gui/input/InputAPIAddWindow.h
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
#pragma once
|
||||
|
||||
#include "input/api/InputAPI.h"
|
||||
|
||||
#include <optional>
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/panel.h>
|
||||
|
||||
#include "gui/helpers/wxCustomData.h"
|
||||
#include "input/api/Controller.h"
|
||||
|
||||
class wxComboBox;
|
||||
class wxChoice;
|
||||
class wxTextCtrl;
|
||||
|
||||
using wxAPIType = wxCustomData<InputAPI::Type>;
|
||||
|
||||
class InputAPIAddWindow : public wxDialog
|
||||
{
|
||||
public:
|
||||
InputAPIAddWindow(wxWindow* parent, const wxPoint& position, const std::vector<ControllerPtr>& controllers);
|
||||
|
||||
bool is_valid() const { return m_type.has_value() && m_controller != nullptr; }
|
||||
InputAPI::Type get_type() const { return m_type.value(); }
|
||||
std::shared_ptr<ControllerBase> get_controller() const { return m_controller; }
|
||||
|
||||
bool has_custom_settings() const;
|
||||
std::unique_ptr<ControllerProviderSettings> get_settings() const;
|
||||
|
||||
private:
|
||||
void on_add_button(wxCommandEvent& event);
|
||||
void on_close_button(wxCommandEvent& event);
|
||||
|
||||
|
||||
void on_api_selected(wxCommandEvent& event);
|
||||
|
||||
void on_controller_dropdown(wxCommandEvent& event);
|
||||
void on_controller_selected(wxCommandEvent& event);
|
||||
void on_controllers_refreshed(wxCommandEvent& event);
|
||||
|
||||
wxChoice* m_input_api;
|
||||
wxComboBox* m_controller_list;
|
||||
wxButton* m_ok_button;
|
||||
|
||||
wxPanel* m_settings_panel;
|
||||
wxTextCtrl* m_ip, * m_port;
|
||||
|
||||
std::optional<InputAPI::Type> m_type;
|
||||
std::shared_ptr<ControllerBase> m_controller;
|
||||
|
||||
std::vector<ControllerPtr> m_controllers;
|
||||
std::atomic_bool m_search_running = false;
|
||||
};
|
||||
979
src/gui/input/InputSettings2.cpp
Normal file
979
src/gui/input/InputSettings2.cpp
Normal file
|
|
@ -0,0 +1,979 @@
|
|||
#include "gui/input/InputSettings2.h"
|
||||
|
||||
#include <wx/gbsizer.h>
|
||||
|
||||
#include "input/InputManager.h"
|
||||
#include "gui/helpers/wxHelpers.h"
|
||||
#include "gui/helpers/wxControlObject.h"
|
||||
#include "gui/helpers/wxCustomData.h"
|
||||
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/notebook.h>
|
||||
#include <wx/wupdlock.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/combobox.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/statline.h>
|
||||
#include <wx/bmpbuttn.h>
|
||||
|
||||
#include "config/ActiveSettings.h"
|
||||
#include "gui/input/InputAPIAddWindow.h"
|
||||
#include "input/ControllerFactory.h"
|
||||
|
||||
#include "gui/input/panels/VPADInputPanel.h"
|
||||
#include "gui/input/panels/ProControllerInputPanel.h"
|
||||
|
||||
#include "gui/input/settings/DefaultControllerSettings.h"
|
||||
#include "gui/input/panels/ClassicControllerInputPanel.h"
|
||||
#include "gui/input/panels/WiimoteInputPanel.h"
|
||||
#include "gui/input/settings/WiimoteControllerSettings.h"
|
||||
#include "util/EventService.h"
|
||||
|
||||
#if BOOST_OS_LINUX
|
||||
#include "resource/linux/resources.h"
|
||||
#endif
|
||||
|
||||
bool g_inputConfigWindowHasFocus = false;
|
||||
|
||||
// _("<profile name>")
|
||||
const wxString kDefaultProfileName = "<profile name>";
|
||||
|
||||
using wxTypeData = wxCustomData<EmulatedController::Type>;
|
||||
using wxControllerData = wxCustomData<ControllerPtr>;
|
||||
|
||||
struct ControllerPage
|
||||
{
|
||||
EmulatedControllerPtr m_controller;
|
||||
|
||||
// profiles
|
||||
wxComboBox* m_profiles;
|
||||
wxButton* m_profile_load, * m_profile_save, * m_profile_delete;
|
||||
wxStaticText* m_profile_status;
|
||||
|
||||
// emulated controller
|
||||
wxComboBox* m_emulated_controller;
|
||||
|
||||
// controller api
|
||||
wxComboBox* m_controllers;
|
||||
wxButton* m_controller_api_add, *m_controller_api_remove;
|
||||
|
||||
wxButton* m_controller_settings, * m_controller_calibrate, *m_controller_clear;
|
||||
wxBitmapButton* m_controller_connected;
|
||||
|
||||
// panel
|
||||
std::array<InputPanel*, EmulatedController::Type::MAX> m_panels{};
|
||||
};
|
||||
using wxControllerPageData = wxCustomData<ControllerPage>;
|
||||
|
||||
|
||||
InputSettings2::InputSettings2(wxWindow* parent)
|
||||
: wxDialog(parent, wxID_ANY, _("Input settings"))
|
||||
{
|
||||
this->SetSizeHints(wxDefaultSize, wxDefaultSize);
|
||||
|
||||
g_inputConfigWindowHasFocus = true;
|
||||
|
||||
m_connected = wxBITMAP_PNG(INPUT_CONNECTED);
|
||||
m_disconnected = wxBITMAP_PNG(INPUT_DISCONNECTED);
|
||||
m_low_battery = wxBITMAP_PNG(INPUT_LOW_BATTERY);
|
||||
|
||||
auto* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_notebook = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0);
|
||||
for(size_t i = 0; i < InputManager::kMaxController; ++i)
|
||||
{
|
||||
auto* page = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
page->SetClientObject(nullptr); // force internal type to client object
|
||||
m_notebook->AddPage(page, wxStringFormat2(_("Controller {}"), i + 1));
|
||||
}
|
||||
|
||||
m_notebook->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &InputSettings2::on_controller_page_changed, this);
|
||||
sizer->Add(m_notebook, 1, wxEXPAND);
|
||||
|
||||
m_notebook->SetSelection(0);
|
||||
auto* first_page = initialize_page(0);
|
||||
|
||||
// init first/default page for fitting size
|
||||
auto* page_data = (wxControllerPageData*)first_page->GetClientObject();
|
||||
auto* panel = new VPADInputPanel(first_page);
|
||||
page_data->ref().m_panels[EmulatedController::Type::VPAD] = panel;
|
||||
|
||||
auto* first_page_sizer = dynamic_cast<wxGridBagSizer*>(first_page->GetSizer());
|
||||
auto* panel_sizer = first_page_sizer->FindItemAtPosition(wxGBPosition(7, 0))->GetSizer();
|
||||
panel_sizer->Add(panel, 0, wxEXPAND);
|
||||
|
||||
panel->Show();
|
||||
first_page->Layout();
|
||||
|
||||
SetSizer(sizer);
|
||||
Layout();
|
||||
Fit();
|
||||
|
||||
panel->Hide();
|
||||
|
||||
update_state();
|
||||
|
||||
Bind(wxEVT_TIMER, &InputSettings2::on_timer, this);
|
||||
|
||||
m_timer = new wxTimer(this);
|
||||
m_timer->Start(100);
|
||||
|
||||
m_controller_changed = EventService::instance().connect<Events::ControllerChanged>(&InputSettings2::on_controller_changed, this);
|
||||
}
|
||||
|
||||
InputSettings2::~InputSettings2()
|
||||
{
|
||||
m_controller_changed.disconnect();
|
||||
|
||||
g_inputConfigWindowHasFocus = false;
|
||||
m_timer->Stop();
|
||||
InputManager::instance().save();
|
||||
}
|
||||
|
||||
|
||||
wxWindow* InputSettings2::initialize_page(size_t index)
|
||||
{
|
||||
auto* page = m_notebook->GetPage(index);
|
||||
if (page->GetClientObject()) // already initialized
|
||||
return page;
|
||||
|
||||
page->Bind(wxEVT_LEFT_UP, &InputSettings2::on_left_click, this);
|
||||
|
||||
ControllerPage page_data{};
|
||||
const auto emulated_controller = InputManager::instance().get_controller(index);
|
||||
page_data.m_controller = emulated_controller;
|
||||
|
||||
wxWindowUpdateLocker lock(page);
|
||||
auto* sizer = new wxGridBagSizer();
|
||||
|
||||
{
|
||||
// profile
|
||||
sizer->Add(new wxStaticText(page, wxID_ANY, _("Profile"), wxDefaultPosition, wxDefaultSize, 0), wxGBPosition(0, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
auto* profiles = new wxComboBox(page, wxID_ANY, _(kDefaultProfileName));
|
||||
sizer->Add(profiles, wxGBPosition(0, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL | wxEXPAND, 5);
|
||||
|
||||
if (emulated_controller && emulated_controller->has_profile_name())
|
||||
{
|
||||
profiles->SetValue(emulated_controller->get_profile_name());
|
||||
}
|
||||
|
||||
auto* load_bttn = new wxButton(page, wxID_ANY, _("Load"));
|
||||
load_bttn->Disable();
|
||||
sizer->Add(load_bttn, wxGBPosition(0, 2), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
|
||||
auto* save_bttn = new wxButton(page, wxID_ANY, _("Save"));
|
||||
save_bttn->Disable();
|
||||
sizer->Add(save_bttn, wxGBPosition(0, 3), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
|
||||
auto* delete_bttn = new wxButton(page, wxID_ANY, _("Delete"));
|
||||
delete_bttn->Disable();
|
||||
sizer->Add(delete_bttn, wxGBPosition(0, 4), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
|
||||
auto* profile_status = new wxStaticText(page, wxID_ANY, _("controller set by gameprofile. changes won't be saved permanently!"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
profile_status->SetMinSize(wxSize(200, -1));
|
||||
profile_status->Wrap(200);
|
||||
sizer->Add(profile_status, wxGBPosition(0, 5), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL | wxRESERVE_SPACE_EVEN_IF_HIDDEN, 5);
|
||||
|
||||
if(InputManager::instance().is_gameprofile_set(index))
|
||||
{
|
||||
profile_status->SetForegroundColour(wxTheColourDatabase->Find("ERROR"));
|
||||
}
|
||||
else
|
||||
{
|
||||
profile_status->Hide();
|
||||
}
|
||||
|
||||
load_bttn->Bind(wxEVT_BUTTON, &InputSettings2::on_profile_load, this);
|
||||
save_bttn->Bind(wxEVT_BUTTON, &InputSettings2::on_profile_save, this);
|
||||
delete_bttn->Bind(wxEVT_BUTTON, &InputSettings2::on_profile_delete, this);
|
||||
|
||||
profiles->Bind(wxEVT_COMBOBOX_DROPDOWN, &InputSettings2::on_profile_dropdown, this);
|
||||
profiles->Bind(wxEVT_TEXT, &InputSettings2::on_profile_text_changed, this);
|
||||
|
||||
page_data.m_profiles = profiles;
|
||||
page_data.m_profile_load = load_bttn;
|
||||
page_data.m_profile_save = save_bttn;
|
||||
page_data.m_profile_delete = delete_bttn;
|
||||
page_data.m_profile_status = profile_status;
|
||||
}
|
||||
|
||||
sizer->Add(new wxStaticLine(page), wxGBPosition(1, 0), wxGBSpan(1, 6), wxEXPAND);
|
||||
|
||||
{
|
||||
// emulated controller
|
||||
sizer->Add(new wxStaticText(page, wxID_ANY, _("Emulated controller")), wxGBPosition(2, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
|
||||
auto* econtroller_box = new wxComboBox(page, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY);
|
||||
econtroller_box->SetMinSize(wxSize(200, -1));
|
||||
econtroller_box->Bind(wxEVT_COMBOBOX_DROPDOWN, &InputSettings2::on_emulated_controller_dropdown, this);
|
||||
econtroller_box->Bind(wxEVT_COMBOBOX, &InputSettings2::on_emulated_controller_selected, this);
|
||||
|
||||
econtroller_box->AppendString(_("Disabled"));
|
||||
econtroller_box->SetSelection(0);
|
||||
|
||||
sizer->Add(econtroller_box, wxGBPosition(2, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL | wxEXPAND, 5);
|
||||
page_data.m_emulated_controller = econtroller_box;
|
||||
}
|
||||
|
||||
sizer->Add(new wxStaticLine(page), wxGBPosition(3, 0), wxGBSpan(1, 6), wxEXPAND);
|
||||
|
||||
{
|
||||
// controller api
|
||||
sizer->Add(new wxStaticText(page, wxID_ANY, _("Controller")), wxGBPosition(4, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||
|
||||
auto* controllers = new wxComboBox(page, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY);
|
||||
controllers->Bind(wxEVT_COMBOBOX, &InputSettings2::on_controller_selected, this);
|
||||
controllers->Bind(wxEVT_COMBOBOX_DROPDOWN, &InputSettings2::on_controller_dropdown, this);
|
||||
controllers->SetMinSize(wxSize(300, -1));
|
||||
|
||||
page_data.m_controllers = controllers;
|
||||
|
||||
sizer->Add(controllers, wxGBPosition(4, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL | wxEXPAND, 5);
|
||||
|
||||
{
|
||||
// add/remove buttons
|
||||
auto* bttn_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
auto* add_api = new wxButton(page, wxID_ANY, wxT(" + "), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
|
||||
add_api->Bind(wxEVT_BUTTON, &InputSettings2::on_controller_add, this);
|
||||
bttn_sizer->Add(add_api, 0, wxALL, 5);
|
||||
|
||||
auto* remove_api = new wxButton(page, wxID_ANY, wxT(" - "), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
|
||||
remove_api->Bind(wxEVT_BUTTON, &InputSettings2::on_controller_remove, this);
|
||||
bttn_sizer->Add(remove_api, 0, wxALL, 5);
|
||||
|
||||
sizer->Add(bttn_sizer, wxGBPosition(4, 2), wxDefaultSpan, wxEXPAND, 5);
|
||||
|
||||
page_data.m_controller_api_add = add_api;
|
||||
page_data.m_controller_api_remove = remove_api;
|
||||
}
|
||||
|
||||
// controller
|
||||
auto* controller_bttns = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto* settings = new wxButton(page, wxID_ANY, _("Settings"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
settings->Bind(wxEVT_BUTTON, &InputSettings2::on_controller_settings, this);
|
||||
settings->Disable();
|
||||
controller_bttns->Add(settings, 0, wxALL, 5);
|
||||
|
||||
auto* calibrate = new wxButton(page, wxID_ANY, _("Calibrate"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
calibrate->Bind(wxEVT_BUTTON, &InputSettings2::on_controller_calibrate, this);
|
||||
calibrate->Disable();
|
||||
controller_bttns->Add(calibrate, 0, wxALL, 5);
|
||||
|
||||
auto* clear = new wxButton(page, wxID_ANY, _("Clear"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
clear->Bind(wxEVT_BUTTON, &InputSettings2::on_controller_clear, this);
|
||||
controller_bttns->Add(clear, 0, wxALL, 5);
|
||||
|
||||
sizer->Add(controller_bttns, wxGBPosition(5, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
auto* connected_button = new wxBitmapButton(page, wxID_ANY, m_disconnected);
|
||||
connected_button->Bind(wxEVT_BUTTON, &InputSettings2::on_controller_connect, this);
|
||||
connected_button->SetToolTip(_("Test if the controller is connected"));
|
||||
sizer->Add(connected_button, wxGBPosition(5, 2), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL, 5); // TODO replace with icon
|
||||
|
||||
page_data.m_controller_settings = settings;
|
||||
page_data.m_controller_calibrate = calibrate;
|
||||
page_data.m_controller_clear = clear;
|
||||
page_data.m_controller_connected = connected_button;
|
||||
|
||||
}
|
||||
|
||||
sizer->Add(new wxStaticLine(page), wxGBPosition(6, 0), wxGBSpan(1, 6), wxEXPAND);
|
||||
|
||||
auto* panel_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
sizer->Add(panel_sizer, wxGBPosition(7, 0), wxGBSpan(1, 6), wxEXPAND | wxALL, 5);
|
||||
|
||||
page->SetSizer(sizer);
|
||||
page->Layout();
|
||||
|
||||
page->SetClientObject(new wxCustomData(page_data));
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
std::pair<size_t, size_t> InputSettings2::get_emulated_controller_types() const
|
||||
{
|
||||
size_t vpad = 0, wpad = 0;
|
||||
for(size_t i = 0; i < m_notebook->GetPageCount(); ++i)
|
||||
{
|
||||
auto* page = m_notebook->GetPage(i);
|
||||
auto* page_data = (wxControllerPageData*)page->GetClientObject();
|
||||
if (!page_data)
|
||||
continue;
|
||||
|
||||
if (!page_data->ref().m_controller) // = disabled
|
||||
continue;
|
||||
|
||||
const auto api_type = page_data->ref().m_controller->type();
|
||||
if (api_type)
|
||||
continue;
|
||||
|
||||
if (api_type == EmulatedController::VPAD)
|
||||
++vpad;
|
||||
else
|
||||
++wpad;
|
||||
}
|
||||
|
||||
return std::make_pair(vpad, wpad);
|
||||
}
|
||||
|
||||
std::shared_ptr<ControllerBase> InputSettings2::get_active_controller() const
|
||||
{
|
||||
auto& page_data = get_current_page_data();
|
||||
|
||||
const auto selection = page_data.m_controllers->GetSelection();
|
||||
if(selection != wxNOT_FOUND)
|
||||
{
|
||||
if(auto* controller = (wxControllerData*)page_data.m_controllers->GetClientObject(selection))
|
||||
return controller->ref();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool InputSettings2::has_settings(InputAPI::Type type)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case InputAPI::Keyboard:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void InputSettings2::update_state()
|
||||
{
|
||||
auto* page = m_notebook->GetCurrentPage();
|
||||
wxWindowUpdateLocker lock(page);
|
||||
|
||||
auto* page_data_ptr = (wxControllerPageData*)page->GetClientObject();
|
||||
wxASSERT(page_data_ptr);
|
||||
auto& page_data = page_data_ptr->ref();
|
||||
|
||||
page_data.m_profile_status->Hide();
|
||||
|
||||
EmulatedControllerPtr emulated_controller = page_data.m_controller;
|
||||
auto has_controllers = false;
|
||||
|
||||
// update emulated
|
||||
if(emulated_controller)
|
||||
{
|
||||
has_controllers = !emulated_controller->get_controllers().empty();
|
||||
|
||||
const auto emulated_type = emulated_controller->type();
|
||||
int index = page_data.m_emulated_controller->Append(to_wxString(emulated_controller->type_to_string(emulated_type)));
|
||||
page_data.m_emulated_controller->SetSelection(index);
|
||||
|
||||
const auto controller_selection = page_data.m_controllers->GetStringSelection();
|
||||
page_data.m_controllers->Clear();
|
||||
if (has_controllers)
|
||||
{
|
||||
for (const auto& c : emulated_controller->get_controllers())
|
||||
{
|
||||
page_data.m_controllers->Append(fmt::format("{} [{}]", c->display_name(), c->api_name()), new wxCustomData(c));
|
||||
}
|
||||
|
||||
if (page_data.m_controllers->GetCount() > 0)
|
||||
{
|
||||
page_data.m_controllers->SetSelection(0);
|
||||
if (!controller_selection.empty())
|
||||
page_data.m_controllers->SetStringSelection(controller_selection);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
page_data.m_emulated_controller->SetValue(_("Disabled"));
|
||||
}
|
||||
|
||||
ControllerPtr controller;
|
||||
if (page_data.m_controllers->GetSelection() != wxNOT_FOUND)
|
||||
{
|
||||
if (const auto data = (wxControllerData*)page_data.m_controllers->GetClientObject(page_data.m_controllers->GetSelection()))
|
||||
controller = data->ref();
|
||||
}
|
||||
|
||||
if (controller && controller->is_connected())
|
||||
page_data.m_controller_connected->SetBitmap(m_connected);
|
||||
else
|
||||
page_data.m_controller_connected->SetBitmap(m_disconnected);
|
||||
|
||||
// update controller
|
||||
page_data.m_controller_calibrate->Enable(has_controllers);
|
||||
page_data.m_controller_api_remove->Enable(has_controllers);
|
||||
page_data.m_controller_settings->Enable(controller && has_settings(controller->api()));
|
||||
|
||||
// update settings
|
||||
// update panel
|
||||
// test if we need to update to correct panel
|
||||
std::optional<EmulatedController::Type> active_api{};
|
||||
for(auto i = 0; i < EmulatedController::Type::MAX; ++i)
|
||||
{
|
||||
if(page_data.m_panels[i] && page_data.m_panels[i]->IsShown())
|
||||
{
|
||||
active_api = (EmulatedController::Type)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// disabled and no emulated controller
|
||||
if (!active_api && !emulated_controller)
|
||||
return;
|
||||
|
||||
// enabled correct panel for active controller
|
||||
if (active_api && emulated_controller && emulated_controller->type() == active_api.value())
|
||||
return;
|
||||
|
||||
// hide all panels
|
||||
for (auto* panel : page_data.m_panels)
|
||||
{
|
||||
if (panel)
|
||||
panel->Hide();
|
||||
}
|
||||
|
||||
// show required panel
|
||||
if (emulated_controller)
|
||||
{
|
||||
auto* sizer = dynamic_cast<wxGridBagSizer*>(page->GetSizer());
|
||||
wxASSERT(sizer);
|
||||
|
||||
const auto type = page_data.m_controller->type();
|
||||
InputPanel* panel = page_data.m_panels[type];
|
||||
if (!panel)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case EmulatedController::Type::VPAD:
|
||||
panel = new VPADInputPanel(page);
|
||||
break;
|
||||
case EmulatedController::Pro:
|
||||
panel = new ProControllerInputPanel(page);
|
||||
break;
|
||||
case EmulatedController::Classic:
|
||||
panel = new ClassicControllerInputPanel(page);
|
||||
break;
|
||||
case EmulatedController::Wiimote:
|
||||
panel = new WiimoteInputPanel(page);
|
||||
break;
|
||||
default:
|
||||
cemu_assert_debug(false);
|
||||
return;
|
||||
}
|
||||
|
||||
page_data.m_panels[type] = panel;
|
||||
|
||||
auto* panel_sizer = sizer->FindItemAtPosition(wxGBPosition(7, 0))->GetSizer();
|
||||
wxASSERT(panel_sizer);
|
||||
panel_sizer->Add(panel, 0, wxEXPAND);
|
||||
}
|
||||
|
||||
panel->load_controller(page_data.m_controller);
|
||||
if (has_controllers)
|
||||
panel->set_selected_controller(emulated_controller, emulated_controller->get_controllers()[0]);
|
||||
|
||||
panel->Show();
|
||||
page->wxWindowBase::Layout();
|
||||
page->wxWindow::Update();
|
||||
}
|
||||
}
|
||||
|
||||
void InputSettings2::on_controller_changed()
|
||||
{
|
||||
for(auto i = 0 ; i < m_notebook->GetPageCount(); ++i)
|
||||
{
|
||||
auto* page = m_notebook->GetPage(i);
|
||||
if (!page)
|
||||
continue;
|
||||
|
||||
auto* page_data_ptr = (wxControllerPageData*)page->GetClientObject();
|
||||
if (!page_data_ptr)
|
||||
continue;
|
||||
|
||||
const auto& page_data = page_data_ptr->ref();
|
||||
if (page_data.m_controllers->GetSelection() != wxNOT_FOUND)
|
||||
{
|
||||
if (const auto data = (wxControllerData*)page_data.m_controllers->GetClientObject(page_data.m_controllers->GetSelection()))
|
||||
{
|
||||
if (const auto controller = data->ref())
|
||||
{
|
||||
if (controller->connect())
|
||||
page_data.m_controller_connected->SetBitmap(m_connected);
|
||||
else
|
||||
page_data.m_controller_connected->SetBitmap(m_disconnected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputSettings2::on_notebook_page_changed(wxBookCtrlEvent& event)
|
||||
{
|
||||
initialize_page(event.GetSelection());
|
||||
update_state();
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void InputSettings2::on_timer(wxTimerEvent&)
|
||||
{
|
||||
auto& page_data = get_current_page_data();
|
||||
if (!page_data.m_controller) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* panel = page_data.m_panels[page_data.m_controller->type()];
|
||||
if (!panel)
|
||||
return;
|
||||
|
||||
auto controller = get_active_controller();
|
||||
if (controller) {
|
||||
panel->on_timer(page_data.m_controller, controller);
|
||||
}
|
||||
}
|
||||
|
||||
void InputSettings2::on_left_click(wxMouseEvent& event)
|
||||
{
|
||||
event.Skip();
|
||||
|
||||
auto& page_data = get_current_page_data();
|
||||
if (!page_data.m_controller) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* panel = page_data.m_panels[page_data.m_controller->type()];
|
||||
if (!panel)
|
||||
return;
|
||||
|
||||
panel->on_left_click(event);
|
||||
}
|
||||
|
||||
void InputSettings2::on_profile_dropdown(wxCommandEvent& event)
|
||||
{
|
||||
auto* profile_names = dynamic_cast<wxComboBox*>(event.GetEventObject());
|
||||
wxASSERT(profile_names);
|
||||
wxWindowUpdateLocker lock(profile_names);
|
||||
|
||||
const auto selected_value = profile_names->GetStringSelection();
|
||||
profile_names->Clear();
|
||||
|
||||
for(const auto& profile : InputManager::get_profiles())
|
||||
{
|
||||
profile_names->Append(wxString::FromUTF8(profile));
|
||||
}
|
||||
|
||||
profile_names->SetStringSelection(selected_value);
|
||||
}
|
||||
|
||||
void InputSettings2::on_profile_text_changed(wxCommandEvent& event)
|
||||
{
|
||||
auto* profile_names = dynamic_cast<wxComboBox*>(event.GetEventObject());
|
||||
wxASSERT(profile_names);
|
||||
|
||||
auto& page_data = get_current_page_data();
|
||||
|
||||
const auto selection = page_data.m_emulated_controller->GetStringSelection();
|
||||
|
||||
// load_bttn, save_bttn, delete_bttn, profile_status
|
||||
const auto text = event.GetString();
|
||||
const auto text_str = from_wxString(text);
|
||||
|
||||
const bool valid_name = InputManager::is_valid_profilename(text_str);
|
||||
const bool name_exists = profile_names->FindString(text) != wxNOT_FOUND;
|
||||
|
||||
page_data.m_profile_load->Enable(name_exists);
|
||||
page_data.m_profile_save->Enable(valid_name);
|
||||
page_data.m_profile_delete->Enable(name_exists);
|
||||
page_data.m_profile_status->Hide();
|
||||
}
|
||||
|
||||
void InputSettings2::on_profile_load(wxCommandEvent& event)
|
||||
{
|
||||
auto& page_data = get_current_page_data();
|
||||
|
||||
auto* profile_names = page_data.m_profiles;
|
||||
auto* text = page_data.m_profile_status;
|
||||
|
||||
const auto selection = from_wxString(profile_names->GetValue());
|
||||
text->Show();
|
||||
if (selection.empty() || !InputManager::is_valid_profilename(selection))
|
||||
{
|
||||
text->SetLabelText(_("invalid profile name"));
|
||||
text->SetForegroundColour(wxTheColourDatabase->Find("ERROR"));
|
||||
text->Refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto page_index = m_notebook->GetSelection();
|
||||
if (InputManager::instance().load(page_index, selection))
|
||||
{
|
||||
text->SetLabelText(_("profile loaded"));
|
||||
text->SetForegroundColour(wxTheColourDatabase->Find("SUCCESS"));
|
||||
}
|
||||
else
|
||||
{
|
||||
text->SetLabelText(_("couldn't load profile"));
|
||||
text->SetForegroundColour(wxTheColourDatabase->Find("ERROR"));
|
||||
}
|
||||
|
||||
text->Refresh();
|
||||
|
||||
// update controller info
|
||||
page_data.m_controller = InputManager::instance().get_controller(page_index);
|
||||
update_state();
|
||||
}
|
||||
|
||||
void InputSettings2::on_profile_save(wxCommandEvent& event)
|
||||
{
|
||||
auto& page_data = get_current_page_data();
|
||||
|
||||
auto* profile_names = page_data.m_profiles;
|
||||
auto* text = page_data.m_profile_status;
|
||||
|
||||
const auto selection = from_wxString(profile_names->GetValue());
|
||||
text->Show();
|
||||
if (selection.empty() || !InputManager::is_valid_profilename(selection))
|
||||
{
|
||||
text->SetLabelText(_("invalid profile name"));
|
||||
text->SetForegroundColour(wxTheColourDatabase->Find("ERROR"));
|
||||
text->Refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
if(InputManager::instance().save(m_notebook->GetSelection(), selection))
|
||||
{
|
||||
text->SetLabelText(_("profile saved"));
|
||||
text->SetForegroundColour(wxTheColourDatabase->Find("SUCCESS"));
|
||||
}
|
||||
else
|
||||
{
|
||||
text->SetLabelText(_("couldn't save profile"));
|
||||
text->SetForegroundColour(wxTheColourDatabase->Find("ERROR"));
|
||||
}
|
||||
|
||||
text->Refresh();
|
||||
}
|
||||
|
||||
void InputSettings2::on_profile_delete(wxCommandEvent& event)
|
||||
{
|
||||
auto& page_data = get_current_page_data();
|
||||
|
||||
auto* profile_names = page_data.m_profiles;
|
||||
auto* text = page_data.m_profile_status;
|
||||
|
||||
const auto selection = from_wxString(profile_names->GetStringSelection());
|
||||
|
||||
text->Show();
|
||||
if (selection.empty() || !InputManager::is_valid_profilename(selection))
|
||||
{
|
||||
text->SetLabelText(_("invalid profile name"));
|
||||
text->SetForegroundColour(wxTheColourDatabase->Find("ERROR"));
|
||||
text->Refresh();
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
const fs::path old_path = ActiveSettings::GetPath(fmt::format("controllerProfiles/{}.txt", selection));
|
||||
fs::remove(old_path);
|
||||
|
||||
const fs::path path = ActiveSettings::GetPath(fmt::format("controllerProfiles/{}.xml", selection));
|
||||
fs::remove(path);
|
||||
|
||||
profile_names->ChangeValue(_(kDefaultProfileName));
|
||||
text->SetLabelText(_("profile deleted"));
|
||||
text->SetForegroundColour(wxTheColourDatabase->Find("SUCCESS"));
|
||||
|
||||
page_data.m_profile_load->Disable();
|
||||
page_data.m_profile_save->Disable();
|
||||
page_data.m_profile_delete->Disable();
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
text->SetLabelText(_("can't delete profile"));
|
||||
text->SetForegroundColour(wxTheColourDatabase->Find("ERROR"));
|
||||
}
|
||||
text->Refresh();
|
||||
}
|
||||
|
||||
|
||||
void InputSettings2::on_controller_page_changed(wxBookCtrlEvent& event)
|
||||
{
|
||||
initialize_page(event.GetSelection());
|
||||
update_state();
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void InputSettings2::on_emulated_controller_selected(wxCommandEvent& event)
|
||||
{
|
||||
const auto page_index = m_notebook->GetSelection();
|
||||
auto& page_data = get_current_page_data();
|
||||
|
||||
const auto selection = event.GetSelection();
|
||||
if(selection == 0) // disabled selected
|
||||
{
|
||||
page_data.m_controller = {};
|
||||
InputManager::instance().delete_controller(page_index, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto type_str = from_wxString(event.GetString());
|
||||
try
|
||||
{
|
||||
const auto type = EmulatedController::type_from_string(type_str);
|
||||
// same has already been selected
|
||||
if (page_data.m_controller && page_data.m_controller->type() == type)
|
||||
return;
|
||||
|
||||
// set new controller
|
||||
const auto new_controller = InputManager::instance().set_controller(page_index, type);
|
||||
page_data.m_controller = new_controller;
|
||||
|
||||
// append controllers if some were already added before
|
||||
if (new_controller->get_controllers().empty())
|
||||
{
|
||||
// test if we had no emulated controller before but still assigned controllers we want to transfer now
|
||||
for (uint32 i = 0; i < page_data.m_controllers->GetCount(); ++i)
|
||||
{
|
||||
if (auto* controller = (wxControllerData*)page_data.m_controllers->GetClientObject(i))
|
||||
{
|
||||
new_controller->add_controller(controller->ref());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set default mappings if any controllers available
|
||||
for(const auto& c: new_controller->get_controllers())
|
||||
{
|
||||
new_controller->set_default_mapping(c);
|
||||
}
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
cemu_assert_debug(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
update_state();
|
||||
}
|
||||
|
||||
void InputSettings2::on_emulated_controller_dropdown(wxCommandEvent& event)
|
||||
{
|
||||
auto* emulated_controllers = dynamic_cast<wxComboBox*>(event.GetEventObject());
|
||||
wxASSERT(emulated_controllers);
|
||||
|
||||
wxWindowUpdateLocker lock(emulated_controllers);
|
||||
|
||||
bool is_gamepad_selected = false;
|
||||
const auto selected = emulated_controllers->GetSelection();
|
||||
const auto selected_value = emulated_controllers->GetStringSelection();
|
||||
if(selected != wxNOT_FOUND)
|
||||
{
|
||||
is_gamepad_selected = selected_value == to_wxString(EmulatedController::type_to_string(EmulatedController::Type::VPAD));
|
||||
}
|
||||
|
||||
const auto [vpad_count, wpad_count] = get_emulated_controller_types();
|
||||
|
||||
emulated_controllers->Clear();
|
||||
emulated_controllers->AppendString(_("Disabled"));
|
||||
|
||||
if (vpad_count < InputManager::kMaxVPADControllers || is_gamepad_selected)
|
||||
emulated_controllers->Append(to_wxString(EmulatedController::type_to_string(EmulatedController::Type::VPAD)));
|
||||
|
||||
if (wpad_count < InputManager::kMaxWPADControllers || !is_gamepad_selected)
|
||||
{
|
||||
emulated_controllers->AppendString(to_wxString(EmulatedController::type_to_string(EmulatedController::Type::Pro)));
|
||||
emulated_controllers->AppendString(to_wxString(EmulatedController::type_to_string(EmulatedController::Type::Classic)));
|
||||
emulated_controllers->AppendString(to_wxString(EmulatedController::type_to_string(EmulatedController::Type::Wiimote)));
|
||||
}
|
||||
|
||||
emulated_controllers->SetStringSelection(selected_value);
|
||||
}
|
||||
|
||||
void InputSettings2::on_controller_selected(wxCommandEvent& event)
|
||||
{
|
||||
auto& page_data = get_current_page_data();
|
||||
|
||||
const auto enabled = event.GetSelection() != wxNOT_FOUND;
|
||||
page_data.m_controller_api_remove->Enable(enabled);
|
||||
// page_data->ref().m_controller_list->Clear();
|
||||
if(enabled)
|
||||
{
|
||||
// get selected controller if any todo
|
||||
if (auto* controller = (wxControllerData*)page_data.m_controllers->GetClientObject(event.GetSelection()))
|
||||
{
|
||||
page_data.m_controller_settings->Enable(has_settings(controller->ref()->api()));
|
||||
|
||||
if(page_data.m_controller)
|
||||
{
|
||||
page_data.m_panels[page_data.m_controller->type()]->set_selected_controller(page_data.m_controller, controller->ref());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void InputSettings2::on_controller_dropdown(wxCommandEvent& event)
|
||||
{
|
||||
if(auto* controllers = dynamic_cast<wxComboBox*>(event.GetEventObject()))
|
||||
{
|
||||
if(controllers->GetCount()== 0)
|
||||
{
|
||||
on_controller_add(event);
|
||||
controllers->SetSelection(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ControllerPage& InputSettings2::get_current_page_data() const
|
||||
{
|
||||
auto* page = m_notebook->GetCurrentPage();
|
||||
auto* page_data_ptr = (wxControllerPageData*)page->GetClientObject();
|
||||
wxASSERT(page_data_ptr);
|
||||
return page_data_ptr->ref();
|
||||
}
|
||||
|
||||
void InputSettings2::on_controller_connect(wxCommandEvent& event)
|
||||
{
|
||||
auto& page_data = get_current_page_data();
|
||||
|
||||
if (page_data.m_controllers->GetSelection() != wxNOT_FOUND)
|
||||
{
|
||||
if (const auto data = (wxControllerData*)page_data.m_controllers->GetClientObject(page_data.m_controllers->GetSelection()))
|
||||
{
|
||||
if(const auto controller = data->ref())
|
||||
{
|
||||
if(controller->connect())
|
||||
page_data.m_controller_connected->SetBitmap(m_connected);
|
||||
else
|
||||
page_data.m_controller_connected->SetBitmap(m_disconnected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputSettings2::on_controller_add(wxCommandEvent& event)
|
||||
{
|
||||
auto& page_data = get_current_page_data();
|
||||
|
||||
std::vector<ControllerPtr> controllers;
|
||||
controllers.reserve(page_data.m_controllers->GetCount());
|
||||
for(uint32 i = 0; i < page_data.m_controllers->GetCount(); ++i)
|
||||
{
|
||||
if (auto* controller = (wxControllerData*)page_data.m_controllers->GetClientObject(i))
|
||||
controllers.emplace_back(controller->ref());
|
||||
}
|
||||
|
||||
InputAPIAddWindow wnd(this, wxGetMousePosition() + wxSize(5, 5), controllers);
|
||||
if (wnd.ShowModal() != wxID_OK)
|
||||
return;
|
||||
|
||||
wxASSERT(wnd.is_valid());
|
||||
|
||||
const auto controller = wnd.get_controller();
|
||||
|
||||
const auto api_type = wnd.get_type();
|
||||
controller->connect();
|
||||
const int index = page_data.m_controllers->Append(fmt::format("{} [{}]", controller->display_name(), to_string(api_type)), new wxCustomData(controller));
|
||||
|
||||
page_data.m_controllers->Select(index);
|
||||
|
||||
if(page_data.m_controller)
|
||||
{
|
||||
page_data.m_controller->add_controller(controller);
|
||||
|
||||
const auto type = page_data.m_controller->type();
|
||||
// if first controller and we got no mappings, add default mappings
|
||||
if(page_data.m_controller->set_default_mapping(controller))
|
||||
page_data.m_panels[type]->load_controller(page_data.m_controller);
|
||||
|
||||
page_data.m_panels[type]->set_selected_controller(page_data.m_controller, controller);
|
||||
}
|
||||
|
||||
update_state();
|
||||
}
|
||||
|
||||
void InputSettings2::on_controller_remove(wxCommandEvent& event)
|
||||
{
|
||||
auto& page_data = get_current_page_data();
|
||||
|
||||
auto* api_box = page_data.m_controllers;
|
||||
int selection = api_box->GetSelection();
|
||||
if (selection == wxNOT_FOUND)
|
||||
return;
|
||||
|
||||
if (page_data.m_controller) {
|
||||
if (auto* controller = (wxControllerData*)page_data.m_controllers->GetClientObject(selection))
|
||||
{
|
||||
page_data.m_controller->remove_controller(controller->ref());
|
||||
page_data.m_panels[page_data.m_controller->type()]->load_controller(page_data.m_controller);
|
||||
}
|
||||
}
|
||||
|
||||
page_data.m_panels[page_data.m_controller->type()]->reset_colours();
|
||||
|
||||
api_box->Delete(selection);
|
||||
api_box->Refresh();
|
||||
|
||||
update_state();
|
||||
|
||||
if (api_box->GetCount() > 0)
|
||||
{
|
||||
selection = selection > 0 ? (selection - 1) : 0;
|
||||
api_box->SetSelection(selection);
|
||||
}
|
||||
|
||||
update_state();
|
||||
}
|
||||
|
||||
void InputSettings2::on_controller_calibrate(wxCommandEvent& event)
|
||||
{
|
||||
if(const auto controller = get_active_controller())
|
||||
controller->calibrate();
|
||||
}
|
||||
|
||||
void InputSettings2::on_controller_clear(wxCommandEvent& event)
|
||||
{
|
||||
auto& page_data = get_current_page_data();
|
||||
if (page_data.m_controller) {
|
||||
const auto type = page_data.m_controller->type();
|
||||
|
||||
page_data.m_panels[type]->reset_configuration();
|
||||
page_data.m_controller->clear_mappings();
|
||||
}
|
||||
}
|
||||
|
||||
void InputSettings2::on_controller_settings(wxCommandEvent& event)
|
||||
{
|
||||
auto controller = get_active_controller();
|
||||
if (!controller)
|
||||
return;
|
||||
|
||||
switch(controller->api())
|
||||
{
|
||||
|
||||
case InputAPI::DirectInput:
|
||||
case InputAPI::XInput:
|
||||
case InputAPI::GameCube:
|
||||
case InputAPI::WGIGamepad:
|
||||
case InputAPI::WGIRawController:
|
||||
case InputAPI::SDLController:
|
||||
case InputAPI::DSUClient:
|
||||
{
|
||||
DefaultControllerSettings wnd(this, wxGetMousePosition() + wxSize(5, 5), controller);
|
||||
wnd.ShowModal();
|
||||
break;
|
||||
}
|
||||
|
||||
case InputAPI::Keyboard: break;
|
||||
|
||||
#if BOOST_OS_WINDOWS
|
||||
case InputAPI::Wiimote: {
|
||||
const auto wiimote = std::dynamic_pointer_cast<NativeWiimoteController>(controller);
|
||||
wxASSERT(wiimote);
|
||||
WiimoteControllerSettings wnd(this, wxGetMousePosition() + wxSize(5, 5), wiimote);
|
||||
wnd.ShowModal();
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
74
src/gui/input/InputSettings2.h
Normal file
74
src/gui/input/InputSettings2.h
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#pragma once
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/notebook.h>
|
||||
#include <wx/timer.h>
|
||||
|
||||
#include "input/api/InputAPI.h"
|
||||
|
||||
#include <boost/signals2/connection.hpp>
|
||||
|
||||
struct ControllerPage;
|
||||
class ControllerBase;
|
||||
|
||||
class InputSettings2 : public wxDialog
|
||||
{
|
||||
public:
|
||||
InputSettings2(wxWindow* parent);
|
||||
~InputSettings2();
|
||||
|
||||
private:
|
||||
wxNotebook* m_notebook;
|
||||
wxTimer* m_timer;
|
||||
|
||||
wxBitmap m_connected, m_disconnected, m_low_battery;
|
||||
|
||||
wxWindow* initialize_page(size_t index);
|
||||
|
||||
// count active <vpad, wpad> controllers
|
||||
std::pair<size_t, size_t> get_emulated_controller_types() const;
|
||||
|
||||
// currently selected controller from active tab
|
||||
std::shared_ptr<ControllerBase> get_active_controller() const;
|
||||
|
||||
bool has_settings(InputAPI::Type type);
|
||||
ControllerPage& get_current_page_data() const;
|
||||
void update_state();
|
||||
|
||||
boost::signals2::connection m_controller_changed;
|
||||
void on_controller_changed();
|
||||
|
||||
// events
|
||||
void on_notebook_page_changed(wxBookCtrlEvent& event);
|
||||
void on_timer(wxTimerEvent& event);
|
||||
|
||||
void on_left_click(wxMouseEvent& event);
|
||||
|
||||
void on_controller_page_changed(wxBookCtrlEvent& event);
|
||||
|
||||
void on_profile_dropdown(wxCommandEvent& event);
|
||||
void on_profile_text_changed(wxCommandEvent& event);
|
||||
|
||||
void on_profile_load(wxCommandEvent& event);
|
||||
void on_profile_save(wxCommandEvent& event);
|
||||
void on_profile_delete(wxCommandEvent& event);
|
||||
|
||||
void on_emulated_controller_selected(wxCommandEvent& event);
|
||||
void on_emulated_controller_dropdown(wxCommandEvent& event);
|
||||
|
||||
void on_controller_selected(wxCommandEvent& event);
|
||||
void on_controller_dropdown(wxCommandEvent& event);
|
||||
void on_controller_connect(wxCommandEvent& event);
|
||||
|
||||
void on_controller_add(wxCommandEvent& event);
|
||||
void on_controller_remove(wxCommandEvent& event);
|
||||
|
||||
void on_controller_calibrate(wxCommandEvent& event);
|
||||
void on_controller_clear(wxCommandEvent& event);
|
||||
void on_controller_settings(wxCommandEvent& event);
|
||||
|
||||
// void on_controller_dropdown(wxCommandEvent& event);
|
||||
// void on_controllers_refreshed(wxCommandEvent& event);
|
||||
|
||||
};
|
||||
|
||||
139
src/gui/input/panels/ClassicControllerInputPanel.cpp
Normal file
139
src/gui/input/panels/ClassicControllerInputPanel.cpp
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
#include "gui/input/panels/ClassicControllerInputPanel.h"
|
||||
|
||||
#include <wx/gbsizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/statline.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/slider.h>
|
||||
|
||||
#include "gui/helpers/wxControlObject.h"
|
||||
#include "input/emulated/ClassicController.h"
|
||||
#include "gui/helpers/wxHelpers.h"
|
||||
#include "gui/components/wxInputDraw.h"
|
||||
|
||||
constexpr ClassicController::ButtonId g_kFirstColumnItems[] = { ClassicController::kButtonId_A, ClassicController::kButtonId_B, ClassicController::kButtonId_X, ClassicController::kButtonId_Y, ClassicController::kButtonId_L, ClassicController::kButtonId_R, ClassicController::kButtonId_ZL, ClassicController::kButtonId_ZR, ClassicController::kButtonId_Plus, ClassicController::kButtonId_Minus };
|
||||
constexpr ClassicController::ButtonId g_kSecondColumnItems[] = { ClassicController::kButtonId_StickL_Up, ClassicController::kButtonId_StickL_Down, ClassicController::kButtonId_StickL_Left, ClassicController::kButtonId_StickL_Right };
|
||||
constexpr ClassicController::ButtonId g_kThirdColumnItems[] = { ClassicController::kButtonId_StickR_Up, ClassicController::kButtonId_StickR_Down, ClassicController::kButtonId_StickR_Left, ClassicController::kButtonId_StickR_Right };
|
||||
constexpr ClassicController::ButtonId g_kFourthRowItems[] = { ClassicController::kButtonId_Up, ClassicController::kButtonId_Down, ClassicController::kButtonId_Left, ClassicController::kButtonId_Right };
|
||||
|
||||
|
||||
ClassicControllerInputPanel::ClassicControllerInputPanel(wxWindow* parent)
|
||||
: InputPanel(parent)
|
||||
{
|
||||
auto bold_font = GetFont();
|
||||
bold_font.MakeBold();
|
||||
|
||||
auto* main_sizer = new wxGridBagSizer();
|
||||
sint32 row = 0;
|
||||
sint32 column = 0;
|
||||
for (const auto& id : g_kFirstColumnItems)
|
||||
{
|
||||
row++;
|
||||
|
||||
main_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(ClassicController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
auto* text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB);
|
||||
text_ctrl->SetClientData(reinterpret_cast<void*>(id));
|
||||
text_ctrl->SetMinSize(wxSize(150, -1));
|
||||
text_ctrl->SetEditable(false);
|
||||
text_ctrl->SetBackgroundColour(kKeyColourNormalMode);
|
||||
bind_hotkey_events(text_ctrl);
|
||||
main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 2), wxGBSpan(11, 1), wxALL | wxEXPAND, 5);
|
||||
|
||||
row = 0;
|
||||
column += 3;
|
||||
|
||||
auto text = new wxStaticText(this, wxID_ANY, _("Left Axis"));
|
||||
text->SetFont(bold_font);
|
||||
main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5);
|
||||
|
||||
for (const auto& id : g_kSecondColumnItems)
|
||||
{
|
||||
row++;
|
||||
|
||||
main_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(ClassicController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
auto* text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB);
|
||||
text_ctrl->SetClientData(reinterpret_cast<void*>(id));
|
||||
text_ctrl->SetMinSize(wxSize(150, -1));
|
||||
text_ctrl->SetEditable(false);
|
||||
text_ctrl->SetBackgroundColour(kKeyColourNormalMode);
|
||||
bind_hotkey_events(text_ctrl);
|
||||
main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
|
||||
row++;
|
||||
|
||||
// input drawer
|
||||
m_left_draw = new wxInputDraw(this, wxID_ANY, wxDefaultPosition, { 60, 60 });
|
||||
main_sizer->Add(m_left_draw, wxGBPosition(row, column + 1), wxGBSpan(2, 1), wxTOP | wxBOTTOM | wxEXPAND | wxALIGN_CENTER, 5);
|
||||
|
||||
main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 3), wxGBSpan(11, 1), wxALL | wxEXPAND, 5);
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
row = 0;
|
||||
column += 4;
|
||||
|
||||
text = new wxStaticText(this, wxID_ANY, _("Right Axis"));
|
||||
text->SetFont(bold_font);
|
||||
main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5);
|
||||
|
||||
for (const auto& id : g_kThirdColumnItems)
|
||||
{
|
||||
row++;
|
||||
|
||||
main_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(ClassicController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
auto* text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB);
|
||||
text_ctrl->SetClientData(reinterpret_cast<void*>(id));
|
||||
text_ctrl->SetMinSize(wxSize(150, -1));
|
||||
text_ctrl->SetEditable(false);
|
||||
text_ctrl->SetBackgroundColour(kKeyColourNormalMode);
|
||||
bind_hotkey_events(text_ctrl);
|
||||
main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
|
||||
row++;
|
||||
|
||||
// input drawer
|
||||
m_right_draw = new wxInputDraw(this, wxID_ANY, wxDefaultPosition, { 60, 60 });
|
||||
main_sizer->Add(m_right_draw, wxGBPosition(row, column + 1), wxGBSpan(2, 1), wxTOP | wxBOTTOM | wxEXPAND | wxALIGN_CENTER, 5);
|
||||
|
||||
main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 3), wxGBSpan(11, 1), wxALL | wxEXPAND, 5);
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
row = 0;
|
||||
column += 4;
|
||||
|
||||
text = new wxStaticText(this, wxID_ANY, _("D-pad"));
|
||||
text->SetFont(bold_font);
|
||||
main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5);
|
||||
|
||||
for (auto id : g_kFourthRowItems)
|
||||
{
|
||||
row++;
|
||||
|
||||
main_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(ClassicController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
auto* text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB);
|
||||
text_ctrl->SetClientData(reinterpret_cast<void*>(id));
|
||||
text_ctrl->SetMinSize(wxSize(150, -1));
|
||||
text_ctrl->SetEditable(false);
|
||||
text_ctrl->SetBackgroundColour(kKeyColourNormalMode);
|
||||
bind_hotkey_events(text_ctrl);
|
||||
main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
SetSizer(main_sizer);
|
||||
Layout();
|
||||
}
|
||||
15
src/gui/input/panels/ClassicControllerInputPanel.h
Normal file
15
src/gui/input/panels/ClassicControllerInputPanel.h
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include "gui/input/panels/InputPanel.h"
|
||||
|
||||
class wxInputDraw;
|
||||
|
||||
class ClassicControllerInputPanel : public InputPanel
|
||||
{
|
||||
public:
|
||||
ClassicControllerInputPanel(wxWindow* parent);
|
||||
|
||||
private:
|
||||
wxInputDraw* m_left_draw, * m_right_draw;
|
||||
};
|
||||
|
||||
287
src/gui/input/panels/InputPanel.cpp
Normal file
287
src/gui/input/panels/InputPanel.cpp
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
#include "gui/input/panels/InputPanel.h"
|
||||
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/wupdlock.h>
|
||||
|
||||
#include "gui/helpers/wxHelpers.h"
|
||||
|
||||
InputPanel::InputPanel(wxWindow* parent)
|
||||
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxNO_BORDER | wxWANTS_CHARS)
|
||||
{
|
||||
Bind(wxEVT_LEFT_UP, &InputPanel::on_left_click, this);
|
||||
}
|
||||
|
||||
void InputPanel::on_timer(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller)
|
||||
{
|
||||
const auto& state = controller->update_state();
|
||||
|
||||
if(m_focused_element == wxID_NONE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto element = dynamic_cast<wxTextCtrl*>(FindWindow(m_focused_element));
|
||||
wxASSERT(element);
|
||||
|
||||
const auto mapping = reinterpret_cast<uint64>(element->GetClientData());
|
||||
|
||||
// reset mapping
|
||||
if(std::exchange(m_right_down, false) || wxGetKeyState(WXK_ESCAPE))
|
||||
{
|
||||
element->SetBackgroundColour(kKeyColourNormalMode);
|
||||
m_color_backup[element->GetId()] = kKeyColourNormalMode;
|
||||
|
||||
emulated_controller->delete_mapping(mapping);
|
||||
if(element->IsEmpty())
|
||||
reset_focused_element();
|
||||
else
|
||||
element->SetValue(wxEmptyString);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static bool s_was_idle = true;
|
||||
if (state.buttons.none()) {
|
||||
s_was_idle = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!s_was_idle) {
|
||||
return;
|
||||
}
|
||||
|
||||
s_was_idle = false;
|
||||
for (size_t i = 0; i < state.buttons.size(); ++i)
|
||||
{
|
||||
if (state.buttons[i])
|
||||
{
|
||||
if (controller->has_axis()) {
|
||||
// test if one axis direction is pressed more than the other
|
||||
if ((i == kAxisXP || i == kAxisXN) && (state.buttons[kAxisYP] || state.buttons[kAxisYN]))
|
||||
{
|
||||
if (std::abs(state.axis.y) > std::abs(state.axis.x))
|
||||
continue;
|
||||
}
|
||||
else if ((i == kAxisYP || i == kAxisYN) && (state.buttons[kAxisXP] || state.buttons[kAxisXN]))
|
||||
{
|
||||
if (std::abs(state.axis.x) > std::abs(state.axis.y))
|
||||
continue;
|
||||
}
|
||||
else if ((i == kRotationXP || i == kRotationXN) && (state.buttons[kRotationYP] || state.buttons[kRotationYN]))
|
||||
{
|
||||
if (std::abs(state.rotation.y) > std::abs(state.rotation.x))
|
||||
continue;
|
||||
}
|
||||
else if ((i == kRotationYP || i == kRotationYN) && (state.buttons[kRotationXP] || state.buttons[kRotationXN]))
|
||||
{
|
||||
if (std::abs(state.rotation.x) > std::abs(state.rotation.y))
|
||||
continue;
|
||||
}
|
||||
else if ((i == kTriggerXP || i == kTriggerXN) && (state.buttons[kTriggerYP] || state.buttons[kTriggerYN]))
|
||||
{
|
||||
if (std::abs(state.trigger.y) > std::abs(state.trigger.x))
|
||||
continue;
|
||||
}
|
||||
else if ((i == kTriggerYP || i == kTriggerYN) && (state.buttons[kTriggerXP] || state.buttons[kTriggerXN]))
|
||||
{
|
||||
if (std::abs(state.trigger.x) > std::abs(state.trigger.y))
|
||||
continue;
|
||||
}
|
||||
|
||||
// ignore too low button values on configuration
|
||||
if (i >= kButtonAxisStart)
|
||||
{
|
||||
if (controller->get_axis_value(i) < 0.33f) {
|
||||
forceLogDebug_printf("skipping since value too low %f", controller->get_axis_value(i));
|
||||
s_was_idle = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emulated_controller->set_mapping(mapping, controller, i);
|
||||
element->SetValue(controller->get_button_name(i));
|
||||
element->SetBackgroundColour(kKeyColourNormalMode);
|
||||
m_color_backup[element->GetId()] = kKeyColourNormalMode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto sibling = get_next_sibling(element))
|
||||
sibling->SetFocus();
|
||||
else // last element reached
|
||||
{
|
||||
reset_focused_element();
|
||||
this->SetFocusIgnoringChildren();
|
||||
}
|
||||
}
|
||||
|
||||
void InputPanel::reset_configuration()
|
||||
{
|
||||
m_color_backup.clear();
|
||||
|
||||
wxWindowUpdateLocker lock(this);
|
||||
for (const auto& c : GetChildren())
|
||||
{
|
||||
if (auto* text = dynamic_cast<wxTextCtrl*>(c))
|
||||
{
|
||||
text->SetValue(wxEmptyString);
|
||||
text->SetBackgroundColour(kKeyColourNormalMode);
|
||||
text->Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputPanel::reset_colours()
|
||||
{
|
||||
m_color_backup.clear();
|
||||
|
||||
wxWindowUpdateLocker lock(this);
|
||||
for (const auto& c : GetChildren())
|
||||
{
|
||||
if (auto* text = dynamic_cast<wxTextCtrl*>(c))
|
||||
{
|
||||
text->SetBackgroundColour(kKeyColourNormalMode);
|
||||
text->Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void InputPanel::load_controller(const EmulatedControllerPtr& controller)
|
||||
{
|
||||
reset_configuration();
|
||||
if(!controller)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(controller->get_controllers().empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
wxWindowUpdateLocker lock(this);
|
||||
for (auto* child : this->GetChildren())
|
||||
{
|
||||
const auto text = dynamic_cast<wxTextCtrl*>(child);
|
||||
if (text == nullptr)
|
||||
continue;
|
||||
|
||||
|
||||
const auto mapping = reinterpret_cast<sint64>(text->GetClientData());
|
||||
if (mapping <= 0)
|
||||
continue;
|
||||
|
||||
auto button_name = controller->get_mapping_name(mapping);
|
||||
#if BOOST_OS_WINDOWS
|
||||
text->SetLabelText(button_name);
|
||||
#else
|
||||
// SetLabelText doesn't seem to work here for some reason on wxGTK
|
||||
text->ChangeValue(button_name);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void InputPanel::set_selected_controller(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller)
|
||||
{
|
||||
wxWindowUpdateLocker lock(this);
|
||||
for (auto* child : this->GetChildren())
|
||||
{
|
||||
const auto text = dynamic_cast<wxTextCtrl*>(child);
|
||||
if (text == nullptr)
|
||||
continue;
|
||||
|
||||
if (text->GetId() == m_focused_element)
|
||||
continue;
|
||||
|
||||
const auto mapping = reinterpret_cast<sint64>(text->GetClientData());
|
||||
if (mapping <= 0)
|
||||
continue;
|
||||
|
||||
const auto mapping_controller = emulated_controller->get_mapping_controller(mapping);
|
||||
if (!mapping_controller)
|
||||
continue;
|
||||
|
||||
text->SetBackgroundColour(*mapping_controller == *controller ? kKeyColourNormalMode : kKeyColourActiveMode);
|
||||
text->Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void InputPanel::bind_hotkey_events(wxTextCtrl* text_ctrl)
|
||||
{
|
||||
text_ctrl->Bind(wxEVT_SET_FOCUS, &InputPanel::on_edit_key_focus, this);
|
||||
text_ctrl->Bind(wxEVT_KILL_FOCUS, &InputPanel::on_edit_key_kill_focus, this);
|
||||
text_ctrl->Bind(wxEVT_RIGHT_DOWN, &InputPanel::on_right_click, this);
|
||||
}
|
||||
|
||||
void InputPanel::on_left_click(wxMouseEvent& event)
|
||||
{
|
||||
if (m_focused_element == wxID_NONE)
|
||||
return;
|
||||
|
||||
const auto focuses_element = FindWindow(m_focused_element);
|
||||
wxASSERT(focuses_element);
|
||||
|
||||
wxFocusEvent focus(wxEVT_KILL_FOCUS, m_focused_element);
|
||||
focus.SetWindow(focuses_element);
|
||||
focuses_element->GetEventHandler()->ProcessEvent(focus);
|
||||
|
||||
this->SetFocusIgnoringChildren();
|
||||
}
|
||||
|
||||
void InputPanel::on_edit_key_focus(wxFocusEvent& event)
|
||||
{
|
||||
auto* text = dynamic_cast<wxTextCtrl*>(event.GetEventObject());
|
||||
wxASSERT(text);
|
||||
|
||||
m_color_backup[text->GetId()] = text->GetBackgroundColour();
|
||||
|
||||
text->SetBackgroundColour(kKeyColourEditMode);
|
||||
#if BOOST_OS_WINDOWS
|
||||
text->HideNativeCaret();
|
||||
#endif
|
||||
text->Refresh();
|
||||
|
||||
m_focused_element = text->GetId();
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void InputPanel::on_edit_key_kill_focus(wxFocusEvent& event)
|
||||
{
|
||||
reset_focused_element();
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void InputPanel::on_right_click(wxMouseEvent& event)
|
||||
{
|
||||
m_right_down = true;
|
||||
if(m_focused_element == wxID_NONE)
|
||||
{
|
||||
auto* text = dynamic_cast<wxTextCtrl*>(event.GetEventObject());
|
||||
wxASSERT(text);
|
||||
text->SetFocus();
|
||||
}
|
||||
}
|
||||
|
||||
bool InputPanel::reset_focused_element()
|
||||
{
|
||||
if (m_focused_element == wxID_NONE)
|
||||
return false;
|
||||
|
||||
auto* prev_element = dynamic_cast<wxTextCtrl*>(FindWindow(m_focused_element));
|
||||
wxASSERT(prev_element);
|
||||
|
||||
if(m_color_backup.find(prev_element->GetId()) != m_color_backup.cend())
|
||||
prev_element->SetBackgroundColour(m_color_backup[prev_element->GetId()]);
|
||||
else
|
||||
prev_element->SetBackgroundColour(kKeyColourNormalMode);
|
||||
|
||||
#if BOOST_OS_WINDOWS
|
||||
prev_element->HideNativeCaret();
|
||||
#endif
|
||||
prev_element->Refresh();
|
||||
|
||||
m_focused_element = wxID_NONE;
|
||||
return true;
|
||||
}
|
||||
45
src/gui/input/panels/InputPanel.h
Normal file
45
src/gui/input/panels/InputPanel.h
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
|
||||
#include <wx/panel.h>
|
||||
|
||||
#include "input/emulated/EmulatedController.h"
|
||||
#include "input/api/Controller.h"
|
||||
|
||||
class ControllerBase;
|
||||
class wxTextCtrl;
|
||||
class wxComboBox;
|
||||
|
||||
class InputPanel : public wxPanel
|
||||
{
|
||||
public:
|
||||
const wxColour kKeyColourNormalMode = 0xfafafa;
|
||||
const wxColour kKeyColourEditMode = 0x99ccff;
|
||||
const wxColour kKeyColourActiveMode = 0xE0E0E0;
|
||||
|
||||
InputPanel(wxWindow* parent);
|
||||
|
||||
ControllerPtr get_active_controller() const;
|
||||
|
||||
virtual void on_timer(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller);
|
||||
void on_left_click(wxMouseEvent& event);
|
||||
|
||||
void reset_configuration();
|
||||
virtual void load_controller(const EmulatedControllerPtr& controller);
|
||||
|
||||
void set_selected_controller(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller_base);
|
||||
void reset_colours();
|
||||
|
||||
protected:
|
||||
void bind_hotkey_events(wxTextCtrl* text_ctrl);
|
||||
|
||||
void on_edit_key_focus(wxFocusEvent& event);
|
||||
void on_edit_key_kill_focus(wxFocusEvent& event);
|
||||
void on_right_click(wxMouseEvent& event);
|
||||
|
||||
bool reset_focused_element();
|
||||
|
||||
bool m_right_down = false;
|
||||
int m_focused_element = wxID_NONE;
|
||||
std::unordered_map<int, wxColour> m_color_backup;
|
||||
};
|
||||
|
||||
156
src/gui/input/panels/ProControllerInputPanel.cpp
Normal file
156
src/gui/input/panels/ProControllerInputPanel.cpp
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
#include "gui/input/panels/ProControllerInputPanel.h"
|
||||
|
||||
#include <wx/gbsizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/statline.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/slider.h>
|
||||
|
||||
#include "gui/helpers/wxControlObject.h"
|
||||
#include "input/emulated/ProController.h"
|
||||
#include "gui/helpers/wxHelpers.h"
|
||||
#include "gui/components/wxInputDraw.h"
|
||||
|
||||
const ProController::ButtonId g_kFirstColumnItems[] = { ProController::kButtonId_A, ProController::kButtonId_B, ProController::kButtonId_X, ProController::kButtonId_Y, ProController::kButtonId_L, ProController::kButtonId_R, ProController::kButtonId_ZL, ProController::kButtonId_ZR, ProController::kButtonId_Plus, ProController::kButtonId_Minus };
|
||||
const ProController::ButtonId g_kSecondColumnItems[] = { ProController::kButtonId_StickL, ProController::kButtonId_StickL_Up, ProController::kButtonId_StickL_Down, ProController::kButtonId_StickL_Left, ProController::kButtonId_StickL_Right };
|
||||
const ProController::ButtonId g_kThirdColumnItems[] = { ProController::kButtonId_StickR, ProController::kButtonId_StickR_Up, ProController::kButtonId_StickR_Down, ProController::kButtonId_StickR_Left, ProController::kButtonId_StickR_Right };
|
||||
const ProController::ButtonId g_kFourthRowItems[] = { ProController::kButtonId_Up, ProController::kButtonId_Down, ProController::kButtonId_Left, ProController::kButtonId_Right };
|
||||
|
||||
|
||||
ProControllerInputPanel::ProControllerInputPanel(wxWindow* parent)
|
||||
: InputPanel(parent)
|
||||
{
|
||||
auto bold_font = GetFont();
|
||||
bold_font.MakeBold();
|
||||
|
||||
auto main_sizer = new wxGridBagSizer();
|
||||
|
||||
sint32 row = 0;
|
||||
sint32 column = 0;
|
||||
for (auto id : g_kFirstColumnItems)
|
||||
{
|
||||
row++;
|
||||
|
||||
main_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(ProController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
auto text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB);
|
||||
text_ctrl->SetClientData((void*)id);
|
||||
text_ctrl->SetMinSize(wxSize(150, -1));
|
||||
text_ctrl->SetEditable(false);
|
||||
text_ctrl->SetBackgroundColour(kKeyColourNormalMode);
|
||||
bind_hotkey_events(text_ctrl);
|
||||
main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 2), wxGBSpan(11, 1), wxALL | wxEXPAND, 5);
|
||||
|
||||
row = 0;
|
||||
column += 3;
|
||||
|
||||
auto text = new wxStaticText(this, wxID_ANY, _("Left Axis"));
|
||||
text->SetFont(bold_font);
|
||||
main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5);
|
||||
|
||||
for (auto id : g_kSecondColumnItems)
|
||||
{
|
||||
row++;
|
||||
|
||||
main_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(ProController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
auto text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB);
|
||||
text_ctrl->SetClientData((void*)id);
|
||||
text_ctrl->SetMinSize(wxSize(150, -1));
|
||||
text_ctrl->SetEditable(false);
|
||||
text_ctrl->SetBackgroundColour(kKeyColourNormalMode);
|
||||
bind_hotkey_events(text_ctrl);
|
||||
main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
|
||||
row++;
|
||||
|
||||
// input drawer
|
||||
m_left_draw = new wxInputDraw(this, wxID_ANY, wxDefaultPosition, { 60, 60 });
|
||||
main_sizer->Add(m_left_draw, wxGBPosition(row, column + 1), wxGBSpan(2, 1), wxTOP | wxBOTTOM | wxEXPAND | wxALIGN_CENTER, 5);
|
||||
|
||||
main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 3), wxGBSpan(11, 1), wxALL | wxEXPAND, 5);
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
row = 0;
|
||||
column += 4;
|
||||
|
||||
text = new wxStaticText(this, wxID_ANY, _("Right Axis"));
|
||||
text->SetFont(bold_font);
|
||||
main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5);
|
||||
|
||||
for (auto id : g_kThirdColumnItems)
|
||||
{
|
||||
row++;
|
||||
|
||||
main_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(ProController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
auto text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB);
|
||||
text_ctrl->SetClientData((void*)id);
|
||||
text_ctrl->SetMinSize(wxSize(150, -1));
|
||||
text_ctrl->SetEditable(false);
|
||||
text_ctrl->SetBackgroundColour(kKeyColourNormalMode);
|
||||
bind_hotkey_events(text_ctrl);
|
||||
main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
|
||||
row++;
|
||||
|
||||
// input drawer
|
||||
m_right_draw = new wxInputDraw(this, wxID_ANY, wxDefaultPosition, { 60, 60 });
|
||||
main_sizer->Add(m_right_draw, wxGBPosition(row, column + 1), wxGBSpan(2, 1), wxTOP | wxBOTTOM | wxEXPAND | wxALIGN_CENTER, 5);
|
||||
|
||||
main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 3), wxGBSpan(11, 1), wxALL | wxEXPAND, 5);
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
row = 0;
|
||||
column += 4;
|
||||
|
||||
text = new wxStaticText(this, wxID_ANY, _("D-pad"));
|
||||
text->SetFont(bold_font);
|
||||
main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5);
|
||||
|
||||
for (auto id : g_kFourthRowItems)
|
||||
{
|
||||
row++;
|
||||
|
||||
main_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(ProController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
auto text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB);
|
||||
text_ctrl->SetClientData((void*)id);
|
||||
text_ctrl->SetMinSize(wxSize(150, -1));
|
||||
text_ctrl->SetEditable(false);
|
||||
text_ctrl->SetBackgroundColour(kKeyColourNormalMode);
|
||||
bind_hotkey_events(text_ctrl);
|
||||
main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
// LoadController(controller);
|
||||
|
||||
SetSizerAndFit(main_sizer);
|
||||
//wxWindow::Show(show);
|
||||
}
|
||||
|
||||
void ProControllerInputPanel::on_timer(const EmulatedControllerPtr& emulated_controller,
|
||||
const ControllerPtr& controller_base)
|
||||
{
|
||||
InputPanel::on_timer(emulated_controller, controller_base);
|
||||
|
||||
if (emulated_controller)
|
||||
{
|
||||
const auto axis = emulated_controller->get_axis();
|
||||
const auto rotation = emulated_controller->get_rotation();
|
||||
|
||||
m_left_draw->SetAxisValue(axis);
|
||||
m_right_draw->SetAxisValue(rotation);
|
||||
}
|
||||
}
|
||||
15
src/gui/input/panels/ProControllerInputPanel.h
Normal file
15
src/gui/input/panels/ProControllerInputPanel.h
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include "gui/input/panels/InputPanel.h"
|
||||
#include "gui/components/wxInputDraw.h"
|
||||
|
||||
class ProControllerInputPanel : public InputPanel
|
||||
{
|
||||
public:
|
||||
ProControllerInputPanel(wxWindow* parent);
|
||||
|
||||
void on_timer(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller) override;
|
||||
|
||||
private:
|
||||
wxInputDraw* m_left_draw, * m_right_draw;
|
||||
};
|
||||
220
src/gui/input/panels/VPADInputPanel.cpp
Normal file
220
src/gui/input/panels/VPADInputPanel.cpp
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
#include "gui/input/panels/VPADInputPanel.h"
|
||||
|
||||
#include <wx/gbsizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/statline.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/slider.h>
|
||||
|
||||
|
||||
#include "gui/helpers/wxControlObject.h"
|
||||
#include "gui/helpers/wxHelpers.h"
|
||||
#include "gui/components/wxInputDraw.h"
|
||||
#include "input/emulated/VPADController.h"
|
||||
|
||||
constexpr VPADController::ButtonId g_kFirstColumnItems[] =
|
||||
{
|
||||
VPADController::kButtonId_A, VPADController::kButtonId_B, VPADController::kButtonId_X, VPADController::kButtonId_Y,
|
||||
VPADController::kButtonId_L, VPADController::kButtonId_R, VPADController::kButtonId_ZL, VPADController::kButtonId_ZR,
|
||||
VPADController::kButtonId_Plus, VPADController::kButtonId_Minus
|
||||
};
|
||||
|
||||
constexpr VPADController::ButtonId g_kSecondColumnItems[] =
|
||||
{
|
||||
VPADController::kButtonId_StickL, VPADController::kButtonId_StickL_Up, VPADController::kButtonId_StickL_Down, VPADController::kButtonId_StickL_Left, VPADController::kButtonId_StickL_Right
|
||||
};
|
||||
|
||||
constexpr VPADController::ButtonId g_kThirdColumnItems[] =
|
||||
{
|
||||
VPADController::kButtonId_StickR, VPADController::kButtonId_StickR_Up, VPADController::kButtonId_StickR_Down, VPADController::kButtonId_StickR_Left, VPADController::kButtonId_StickR_Right
|
||||
};
|
||||
|
||||
constexpr VPADController::ButtonId g_kFourthRowItems[] =
|
||||
{
|
||||
VPADController::kButtonId_Up, VPADController::kButtonId_Down, VPADController::kButtonId_Left, VPADController::kButtonId_Right
|
||||
};
|
||||
|
||||
|
||||
VPADInputPanel::VPADInputPanel(wxWindow* parent)
|
||||
: InputPanel(parent)
|
||||
{
|
||||
auto bold_font = GetFont();
|
||||
bold_font.MakeBold();
|
||||
|
||||
auto* main_sizer = new wxGridBagSizer();
|
||||
|
||||
sint32 row = 0;
|
||||
sint32 column = 0;
|
||||
for (const auto& id : g_kFirstColumnItems)
|
||||
{
|
||||
row++;
|
||||
|
||||
main_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(VPADController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
auto* text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB);
|
||||
text_ctrl->SetClientData((void*)id);
|
||||
text_ctrl->SetMinSize(wxSize(150, -1));
|
||||
text_ctrl->SetEditable(false);
|
||||
text_ctrl->SetBackgroundColour(kKeyColourNormalMode);
|
||||
bind_hotkey_events(text_ctrl);
|
||||
main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 2), wxGBSpan(11, 1), wxALL | wxEXPAND, 5);
|
||||
|
||||
row = 0;
|
||||
column += 3;
|
||||
|
||||
auto text = new wxStaticText(this, wxID_ANY, _("Left Axis"));
|
||||
text->SetFont(bold_font);
|
||||
main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5);
|
||||
|
||||
for (const auto& id : g_kSecondColumnItems)
|
||||
{
|
||||
row++;
|
||||
|
||||
main_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(VPADController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
auto* text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB);
|
||||
text_ctrl->SetClientData((void*)id);
|
||||
text_ctrl->SetMinSize(wxSize(150, -1));
|
||||
text_ctrl->SetEditable(false);
|
||||
text_ctrl->SetBackgroundColour(kKeyColourNormalMode);
|
||||
bind_hotkey_events(text_ctrl);
|
||||
main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
|
||||
row++;
|
||||
|
||||
// input drawer
|
||||
m_left_draw = new wxInputDraw(this, wxID_ANY, wxDefaultPosition, { 60, 60 });
|
||||
main_sizer->Add(m_left_draw, wxGBPosition(row, column + 1), wxGBSpan(2, 1), wxTOP | wxBOTTOM | wxEXPAND | wxALIGN_CENTER, 5);
|
||||
|
||||
main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 3), wxGBSpan(11, 1), wxALL | wxEXPAND, 5);
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
row = 0;
|
||||
column += 4;
|
||||
|
||||
text = new wxStaticText(this, wxID_ANY, _("Right Axis"));
|
||||
text->SetFont(bold_font);
|
||||
main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5);
|
||||
|
||||
for (const auto& id : g_kThirdColumnItems)
|
||||
{
|
||||
row++;
|
||||
|
||||
main_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(VPADController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
auto* text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB);
|
||||
text_ctrl->SetClientData((void*)id);
|
||||
text_ctrl->SetMinSize(wxSize(150, -1));
|
||||
text_ctrl->SetEditable(false);
|
||||
text_ctrl->SetBackgroundColour(kKeyColourNormalMode);
|
||||
bind_hotkey_events(text_ctrl);
|
||||
main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
|
||||
row++;
|
||||
|
||||
// input drawer
|
||||
m_right_draw = new wxInputDraw(this, wxID_ANY, wxDefaultPosition, { 60, 60 });
|
||||
main_sizer->Add(m_right_draw, wxGBPosition(row, column + 1), wxGBSpan(2, 1), wxTOP | wxBOTTOM | wxEXPAND | wxALIGN_CENTER, 5);
|
||||
|
||||
// Volume
|
||||
row = 10;
|
||||
|
||||
text = new wxStaticText(this, wxID_ANY, _("Volume"));
|
||||
text->Disable();
|
||||
main_sizer->Add(text, wxGBPosition(row, column), wxDefaultSpan, wxALL, 5);
|
||||
|
||||
auto*m_volume = new wxSlider(this, wxID_ANY, 0, 0, 100);
|
||||
m_volume->Disable();
|
||||
main_sizer->Add(m_volume, wxGBPosition(row, column + 1), wxDefaultSpan, wxTOP | wxBOTTOM | wxEXPAND, 5);
|
||||
|
||||
const auto volume_text = new wxStaticText(this, wxID_ANY, wxString::Format("%d%%", 0));
|
||||
volume_text->Disable();
|
||||
main_sizer->Add(volume_text, wxGBPosition(row, column + 2), wxDefaultSpan, wxALL, 5);
|
||||
m_volume->Bind(wxEVT_SLIDER, &VPADInputPanel::OnVolumeChange, this, wxID_ANY, wxID_ANY, new wxControlObject(volume_text));
|
||||
|
||||
main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 3), wxGBSpan(11, 1), wxALL | wxEXPAND, 5);
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
row = 0;
|
||||
column += 4;
|
||||
|
||||
text = new wxStaticText(this, wxID_ANY, _("D-pad"));
|
||||
text->SetFont(bold_font);
|
||||
main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5);
|
||||
|
||||
for (const auto& id : g_kFourthRowItems)
|
||||
{
|
||||
row++;
|
||||
|
||||
main_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(VPADController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
auto text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB);
|
||||
text_ctrl->SetClientData((void*)id);
|
||||
text_ctrl->SetMinSize(wxSize(150, -1));
|
||||
text_ctrl->SetEditable(false);
|
||||
text_ctrl->SetBackgroundColour(kKeyColourNormalMode);
|
||||
bind_hotkey_events(text_ctrl);
|
||||
main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
|
||||
// Blow Mic
|
||||
row = 9;
|
||||
main_sizer->Add(new wxStaticText(this, wxID_ANY, _("blow mic")), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
auto text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB);
|
||||
text_ctrl->SetClientData((void*)VPADController::kButtonId_Mic);
|
||||
text_ctrl->SetMinSize(wxSize(150, -1));
|
||||
text_ctrl->SetEditable(false);
|
||||
text_ctrl->SetBackgroundColour(kKeyColourNormalMode);
|
||||
bind_hotkey_events(text_ctrl);
|
||||
main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5);
|
||||
row++;
|
||||
|
||||
main_sizer->Add(new wxStaticText(this, wxID_ANY, _("show screen")), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB);
|
||||
text_ctrl->SetClientData((void*)VPADController::kButtonId_Screen);
|
||||
text_ctrl->SetMinSize(wxSize(150, -1));
|
||||
text_ctrl->SetEditable(false);
|
||||
text_ctrl->SetBackgroundColour(kKeyColourNormalMode);
|
||||
bind_hotkey_events(text_ctrl);
|
||||
main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5);
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
//LoadController(controller);
|
||||
|
||||
SetSizer(main_sizer);
|
||||
Layout();
|
||||
|
||||
//SetSizerAndFit(main_sizer);
|
||||
//wxWindow::Show(show);
|
||||
}
|
||||
|
||||
void VPADInputPanel::on_timer(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller_base)
|
||||
{
|
||||
InputPanel::on_timer(emulated_controller, controller_base);
|
||||
|
||||
if(emulated_controller)
|
||||
{
|
||||
const auto axis = emulated_controller->get_axis();
|
||||
const auto rotation = emulated_controller->get_rotation();
|
||||
|
||||
m_left_draw->SetAxisValue(axis);
|
||||
m_right_draw->SetAxisValue(rotation);
|
||||
}
|
||||
}
|
||||
|
||||
void VPADInputPanel::OnVolumeChange(wxCommandEvent& event)
|
||||
{
|
||||
|
||||
}
|
||||
18
src/gui/input/panels/VPADInputPanel.h
Normal file
18
src/gui/input/panels/VPADInputPanel.h
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include "gui/input/panels/InputPanel.h"
|
||||
|
||||
class wxInputDraw;
|
||||
|
||||
class VPADInputPanel : public InputPanel
|
||||
{
|
||||
public:
|
||||
VPADInputPanel(wxWindow* parent);
|
||||
|
||||
void on_timer(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller) override;
|
||||
|
||||
private:
|
||||
void OnVolumeChange(wxCommandEvent& event);
|
||||
|
||||
wxInputDraw* m_left_draw, * m_right_draw;
|
||||
};
|
||||
257
src/gui/input/panels/WiimoteInputPanel.cpp
Normal file
257
src/gui/input/panels/WiimoteInputPanel.cpp
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
#include "gui/input/panels/WiimoteInputPanel.h"
|
||||
|
||||
#include <wx/gbsizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/statline.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/slider.h>
|
||||
#include <wx/checkbox.h>
|
||||
|
||||
#include "gui/helpers/wxControlObject.h"
|
||||
#include "input/emulated/WiimoteController.h"
|
||||
#include "gui/helpers/wxHelpers.h"
|
||||
#include "gui/components/wxInputDraw.h"
|
||||
|
||||
constexpr WiimoteController::ButtonId g_kFirstColumnItems[] =
|
||||
{
|
||||
WiimoteController::kButtonId_A, WiimoteController::kButtonId_B, WiimoteController::kButtonId_1, WiimoteController::kButtonId_2, WiimoteController::kButtonId_Plus, WiimoteController::kButtonId_Minus, WiimoteController::kButtonId_Home
|
||||
};
|
||||
|
||||
constexpr WiimoteController::ButtonId g_kSecondColumnItems[] =
|
||||
{
|
||||
WiimoteController::kButtonId_Up, WiimoteController::kButtonId_Down, WiimoteController::kButtonId_Left, WiimoteController::kButtonId_Right
|
||||
};
|
||||
|
||||
constexpr WiimoteController::ButtonId g_kThirdColumnItems[] =
|
||||
{
|
||||
WiimoteController::kButtonId_Nunchuck_C, WiimoteController::kButtonId_Nunchuck_Z,
|
||||
WiimoteController::kButtonId_None,
|
||||
WiimoteController::kButtonId_Nunchuck_Up,WiimoteController::kButtonId_Nunchuck_Down,WiimoteController::kButtonId_Nunchuck_Left,WiimoteController::kButtonId_Nunchuck_Right
|
||||
};
|
||||
|
||||
WiimoteInputPanel::WiimoteInputPanel(wxWindow* parent)
|
||||
: InputPanel(parent)
|
||||
{
|
||||
auto bold_font = GetFont();
|
||||
bold_font.MakeBold();
|
||||
|
||||
auto* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto* extensions_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
extensions_sizer->Add(new wxStaticText(this, wxID_ANY, _("Extensions:")));
|
||||
extensions_sizer->AddSpacer(10);
|
||||
|
||||
m_motion_plus = new wxCheckBox(this, wxID_ANY, _("MotionPlus"));
|
||||
m_motion_plus->Bind(wxEVT_CHECKBOX, &WiimoteInputPanel::on_extension_change, this);
|
||||
extensions_sizer->Add(m_motion_plus);
|
||||
|
||||
m_nunchuck = new wxCheckBox(this, wxID_ANY, _("Nunchuck"));
|
||||
m_nunchuck->Bind(wxEVT_CHECKBOX, &WiimoteInputPanel::on_extension_change, this);
|
||||
extensions_sizer->Add(m_nunchuck);
|
||||
|
||||
m_classic = new wxCheckBox(this, wxID_ANY, _("Classic"));
|
||||
m_classic->Bind(wxEVT_CHECKBOX, &WiimoteInputPanel::on_extension_change, this);
|
||||
m_classic->Hide();
|
||||
extensions_sizer->Add(m_classic);
|
||||
|
||||
main_sizer->Add(extensions_sizer, 0, wxEXPAND | wxALL, 5);
|
||||
main_sizer->Add(new wxStaticLine(this), 0, wxLEFT | wxRIGHT | wxTOP | wxEXPAND, 5);
|
||||
|
||||
m_item_sizer = new wxGridBagSizer();
|
||||
|
||||
sint32 row = 0;
|
||||
sint32 column = 0;
|
||||
for (const auto& id : g_kFirstColumnItems)
|
||||
{
|
||||
row++;
|
||||
|
||||
m_item_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(WiimoteController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
auto* text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB);
|
||||
text_ctrl->SetClientData((void*)id);
|
||||
text_ctrl->SetMinSize(wxSize(150, -1));
|
||||
text_ctrl->SetEditable(false);
|
||||
text_ctrl->SetBackgroundColour(kKeyColourNormalMode);
|
||||
bind_hotkey_events(text_ctrl);
|
||||
m_item_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
|
||||
m_item_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 2), wxGBSpan(11, 1), wxLEFT | wxRIGHT | wxBOTTOM | wxEXPAND, 5);
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
row = 0;
|
||||
column += 3;
|
||||
|
||||
auto text = new wxStaticText(this, wxID_ANY, _("D-pad"));
|
||||
text->SetFont(bold_font);
|
||||
m_item_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5);
|
||||
|
||||
for (const auto& id : g_kSecondColumnItems)
|
||||
{
|
||||
row++;
|
||||
|
||||
m_item_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(WiimoteController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
auto* text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB);
|
||||
text_ctrl->SetClientData((void*)id);
|
||||
text_ctrl->SetMinSize(wxSize(150, -1));
|
||||
text_ctrl->SetEditable(false);
|
||||
text_ctrl->SetBackgroundColour(kKeyColourNormalMode);
|
||||
bind_hotkey_events(text_ctrl);
|
||||
m_item_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
|
||||
row = 8;
|
||||
// Volume
|
||||
text = new wxStaticText(this, wxID_ANY, _("Volume"));
|
||||
text->Disable();
|
||||
m_item_sizer->Add(text, wxGBPosition(row, column), wxDefaultSpan, wxALL, 5);
|
||||
|
||||
m_volume = new wxSlider(this, wxID_ANY, 0, 0, 100);
|
||||
m_volume->Disable();
|
||||
m_item_sizer->Add(m_volume, wxGBPosition(row, column + 1), wxDefaultSpan, wxTOP | wxBOTTOM | wxEXPAND, 5);
|
||||
|
||||
const auto volume_text = new wxStaticText(this, wxID_ANY, wxString::Format("%d%%", 0));
|
||||
volume_text->Disable();
|
||||
m_item_sizer->Add(volume_text, wxGBPosition(row, column + 2), wxDefaultSpan, wxALL, 5);
|
||||
m_volume->Bind(wxEVT_SLIDER, &WiimoteInputPanel::on_volume_change, this, wxID_ANY, wxID_ANY, new wxControlObject(volume_text));
|
||||
row++;
|
||||
|
||||
m_item_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 3), wxGBSpan(11, 1), wxLEFT | wxRIGHT | wxBOTTOM | wxEXPAND, 5);
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
row = 0;
|
||||
column += 4;
|
||||
|
||||
text = new wxStaticText(this, wxID_ANY, _("Nunchuck"));
|
||||
text->SetFont(bold_font);
|
||||
m_item_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5);
|
||||
|
||||
for (const auto& id : g_kThirdColumnItems)
|
||||
{
|
||||
row++;
|
||||
if (id == WiimoteController::kButtonId_None)
|
||||
continue;
|
||||
|
||||
m_item_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(WiimoteController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
auto* text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB);
|
||||
text_ctrl->SetClientData((void*)id);
|
||||
text_ctrl->SetMinSize(wxSize(150, -1));
|
||||
text_ctrl->SetEditable(false);
|
||||
text_ctrl->SetBackgroundColour(kKeyColourNormalMode);
|
||||
bind_hotkey_events(text_ctrl);
|
||||
text_ctrl->Enable(m_nunchuck->GetValue());
|
||||
m_item_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5);
|
||||
|
||||
m_nunchuck_items.push_back(text_ctrl);
|
||||
}
|
||||
|
||||
|
||||
// input drawer
|
||||
m_draw = new wxInputDraw(this, wxID_ANY, wxDefaultPosition, { 60, 60 });
|
||||
m_draw->Enable(m_nunchuck->GetValue());
|
||||
m_item_sizer->Add(5, 0, wxGBPosition(3, column + 3), wxDefaultSpan, wxTOP | wxBOTTOM | wxEXPAND | wxALIGN_CENTER, 5);
|
||||
m_item_sizer->Add(m_draw, wxGBPosition(3, column + 4), wxGBSpan(4, 1), wxTOP | wxBOTTOM | wxEXPAND | wxALIGN_CENTER, 5);
|
||||
|
||||
m_nunchuck_items.push_back(m_draw);
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
main_sizer->Add(m_item_sizer, 1, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, 5);
|
||||
|
||||
SetSizer(main_sizer);
|
||||
Layout();
|
||||
}
|
||||
|
||||
void WiimoteInputPanel::set_active_device_type(WPADDeviceType type)
|
||||
{
|
||||
m_device_type = type;
|
||||
|
||||
m_motion_plus->SetValue(type == kWAPDevMPLS || type == kWAPDevMPLSFreeStyle || type == kWAPDevMPLSClassic);
|
||||
switch(type)
|
||||
{
|
||||
case kWAPDevFreestyle:
|
||||
case kWAPDevMPLSFreeStyle:
|
||||
m_nunchuck->SetValue(true);
|
||||
m_classic->SetValue(false);
|
||||
for (const auto& item : m_nunchuck_items)
|
||||
{
|
||||
item->Enable(true);
|
||||
}
|
||||
break;
|
||||
|
||||
case kWAPDevClassic:
|
||||
case kWAPDevMPLSClassic:
|
||||
m_nunchuck->SetValue(false);
|
||||
m_classic->SetValue(true);
|
||||
for (const auto& item : m_nunchuck_items)
|
||||
{
|
||||
item->Enable(false);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
m_nunchuck->SetValue(false);
|
||||
m_classic->SetValue(false);
|
||||
for (const auto& item : m_nunchuck_items)
|
||||
{
|
||||
item->Enable(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WiimoteInputPanel::on_volume_change(wxCommandEvent& event)
|
||||
{
|
||||
}
|
||||
|
||||
void WiimoteInputPanel::on_extension_change(wxCommandEvent& event)
|
||||
{
|
||||
const auto obj = dynamic_cast<wxCheckBox*>(event.GetEventObject());
|
||||
wxASSERT(obj);
|
||||
|
||||
if(m_motion_plus->GetValue() && m_nunchuck->GetValue())
|
||||
set_active_device_type(kWAPDevMPLSFreeStyle);
|
||||
else if(m_motion_plus->GetValue() && m_classic->GetValue())
|
||||
set_active_device_type(kWAPDevMPLSClassic);
|
||||
else if (m_motion_plus->GetValue())
|
||||
set_active_device_type(kWAPDevMPLS);
|
||||
else if (m_nunchuck->GetValue())
|
||||
set_active_device_type(kWAPDevFreestyle);
|
||||
else if (m_classic->GetValue())
|
||||
set_active_device_type(kWAPDevClassic);
|
||||
else
|
||||
set_active_device_type(kWAPDevCore);
|
||||
}
|
||||
|
||||
void WiimoteInputPanel::on_timer(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller)
|
||||
{
|
||||
if (emulated_controller)
|
||||
{
|
||||
const auto wiimote = std::dynamic_pointer_cast<WiimoteController>(emulated_controller);
|
||||
wxASSERT(wiimote);
|
||||
|
||||
wiimote->set_device_type(m_device_type);
|
||||
}
|
||||
|
||||
InputPanel::on_timer(emulated_controller, controller);
|
||||
|
||||
if (emulated_controller)
|
||||
{
|
||||
const auto axis = emulated_controller->get_axis();
|
||||
m_draw->SetAxisValue(axis);
|
||||
}
|
||||
}
|
||||
|
||||
void WiimoteInputPanel::load_controller(const EmulatedControllerPtr& emulated_controller)
|
||||
{
|
||||
InputPanel::load_controller(emulated_controller);
|
||||
|
||||
if (emulated_controller) {
|
||||
const auto wiimote = std::dynamic_pointer_cast<WiimoteController>(emulated_controller);
|
||||
wxASSERT(wiimote);
|
||||
set_active_device_type(wiimote->get_device_type());
|
||||
}
|
||||
}
|
||||
41
src/gui/input/panels/WiimoteInputPanel.h
Normal file
41
src/gui/input/panels/WiimoteInputPanel.h
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
|
||||
#include "gui/input/panels/InputPanel.h"
|
||||
#include <wx/slider.h>
|
||||
|
||||
class wxCheckBox;
|
||||
class wxGridBagSizer;
|
||||
class wxInputDraw;
|
||||
|
||||
class WiimoteInputPanel : public InputPanel
|
||||
{
|
||||
public:
|
||||
WiimoteInputPanel(wxWindow* parent);
|
||||
|
||||
void on_timer(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller) override;
|
||||
|
||||
void load_controller(const EmulatedControllerPtr& emulated_controller) override;
|
||||
|
||||
private:
|
||||
wxInputDraw* m_draw;
|
||||
|
||||
WPADDeviceType m_device_type = kWAPDevCore;
|
||||
void set_active_device_type(WPADDeviceType type);
|
||||
|
||||
void on_volume_change(wxCommandEvent& event);
|
||||
void on_extension_change(wxCommandEvent& event);
|
||||
|
||||
wxGridBagSizer* m_item_sizer;
|
||||
|
||||
wxCheckBox* m_nunchuck, * m_classic;
|
||||
wxCheckBox* m_motion_plus;
|
||||
|
||||
wxSlider* m_volume;
|
||||
|
||||
std::vector<wxWindow*> m_nunchuck_items;
|
||||
bool m_extensions_changed;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
312
src/gui/input/settings/DefaultControllerSettings.cpp
Normal file
312
src/gui/input/settings/DefaultControllerSettings.cpp
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
#include "gui/input/settings/DefaultControllerSettings.h"
|
||||
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/slider.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/gbsizer.h>
|
||||
#include <wx/statline.h>
|
||||
#include <wx/checkbox.h>
|
||||
#include <wx/statbox.h>
|
||||
|
||||
#include "gui/helpers/wxControlObject.h"
|
||||
#include "gui/helpers/wxHelpers.h"
|
||||
#include "gui/components/wxInputDraw.h"
|
||||
#include "gui/input/InputAPIAddWindow.h"
|
||||
|
||||
|
||||
DefaultControllerSettings::DefaultControllerSettings(wxWindow* parent, const wxPoint& position, std::shared_ptr<ControllerBase> controller)
|
||||
: wxDialog(parent, wxID_ANY, _("Controller settings"), position, wxDefaultSize,
|
||||
wxDEFAULT_DIALOG_STYLE), m_controller(std::move(controller))
|
||||
{
|
||||
m_settings = m_controller->get_settings();
|
||||
m_rumble_backup = m_settings.rumble;
|
||||
|
||||
this->SetSizeHints(wxDefaultSize, wxDefaultSize);
|
||||
|
||||
auto* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
// options
|
||||
{
|
||||
auto* box = new wxStaticBox(this, wxID_ANY, _("Options"));
|
||||
auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
|
||||
|
||||
// Motion
|
||||
m_use_motion = new wxCheckBox(box, wxID_ANY, _("Use motion"));
|
||||
m_use_motion->SetValue(m_settings.motion);
|
||||
m_use_motion->Enable(m_controller->has_motion());
|
||||
box_sizer->Add(m_use_motion, 0, wxEXPAND | wxALL, 5);
|
||||
|
||||
// Vibration
|
||||
auto* rumbleSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
const auto rumble = (int)(m_settings.rumble * 100);
|
||||
rumbleSizer->Add(new wxStaticText(box, wxID_ANY, _("Rumble")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
m_rumble = new wxSlider(box, wxID_ANY, rumble, 0, 100);
|
||||
rumbleSizer->Add(m_rumble, 1, wxALL | wxEXPAND, 5);
|
||||
|
||||
const auto text = new wxStaticText(box, wxID_ANY, wxString::Format("%d%%", rumble));
|
||||
rumbleSizer->Add(text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
m_rumble->Bind(wxEVT_SLIDER, &DefaultControllerSettings::on_rumble_change, this, wxID_ANY, wxID_ANY, new wxControlObject(text));
|
||||
|
||||
box_sizer->Add(rumbleSizer);
|
||||
|
||||
sizer->Add(box_sizer, 1, wxALL|wxEXPAND, 5);
|
||||
}
|
||||
|
||||
auto* row_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
// Axis
|
||||
{
|
||||
auto* box = new wxStaticBox(this, wxID_ANY, _("Axis"));
|
||||
auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
|
||||
|
||||
{
|
||||
auto* content_sizer = new wxFlexGridSizer(0, 3, 0, 0);
|
||||
|
||||
// Deadzone
|
||||
const auto deadzone = (int)(m_settings.axis.deadzone * 100);
|
||||
content_sizer->Add(new wxStaticText(box, wxID_ANY, _("Deadzone")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
m_axis_deadzone = new wxSlider(box, wxID_ANY, deadzone, 0, 100);
|
||||
content_sizer->Add(m_axis_deadzone, 1, wxALL | wxEXPAND, 5);
|
||||
|
||||
const auto deadzone_text = new wxStaticText(box, wxID_ANY, wxString::Format("%d%%", deadzone));
|
||||
content_sizer->Add(deadzone_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
m_axis_deadzone->Bind(wxEVT_SLIDER, &DefaultControllerSettings::on_deadzone_change, this, wxID_ANY, wxID_ANY,
|
||||
new wxControlObject(deadzone_text));
|
||||
|
||||
|
||||
// Range
|
||||
const auto range = (int)(m_settings.axis.range * 100);
|
||||
content_sizer->Add(new wxStaticText(box, wxID_ANY, _("Range")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
m_axis_range = new wxSlider(box, wxID_ANY, range, 50, 200);
|
||||
content_sizer->Add(m_axis_range, 1, wxALL | wxEXPAND, 5);
|
||||
|
||||
const auto range_text = new wxStaticText(box, wxID_ANY, wxString::Format("%d%%", range));
|
||||
content_sizer->Add(range_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
m_axis_range->Bind(wxEVT_SLIDER, &DefaultControllerSettings::on_range_change, this, wxID_ANY, wxID_ANY,
|
||||
new wxControlObject(range_text));
|
||||
|
||||
content_sizer->AddSpacer(1);
|
||||
m_axis_draw = new wxInputDraw(box, wxID_ANY, wxDefaultPosition, { 60, 60 });
|
||||
content_sizer->Add(m_axis_draw, 0, wxTOP | wxBOTTOM | wxALIGN_CENTER, 5);
|
||||
|
||||
box_sizer->Add(content_sizer, 1, wxEXPAND, 0);
|
||||
}
|
||||
|
||||
row_sizer->Add(box_sizer, 0, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
|
||||
// Rotation
|
||||
{
|
||||
auto* box = new wxStaticBox(this, wxID_ANY, _("Rotation"));
|
||||
auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
|
||||
|
||||
{
|
||||
auto* content_sizer = new wxFlexGridSizer(0, 3, 0, 0);
|
||||
|
||||
// Deadzone
|
||||
const auto deadzone = (int)(m_settings.rotation.deadzone * 100);
|
||||
content_sizer->Add(new wxStaticText(box, wxID_ANY, _("Deadzone")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
m_rotation_deadzone = new wxSlider(box, wxID_ANY, deadzone, 0, 100);
|
||||
content_sizer->Add(m_rotation_deadzone, 1, wxALL | wxEXPAND, 5);
|
||||
|
||||
const auto deadzone_text = new wxStaticText(box, wxID_ANY, wxString::Format("%d%%", deadzone));
|
||||
content_sizer->Add(deadzone_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
m_rotation_deadzone->Bind(wxEVT_SLIDER, &DefaultControllerSettings::on_deadzone_change, this, wxID_ANY, wxID_ANY,
|
||||
new wxControlObject(deadzone_text));
|
||||
|
||||
|
||||
// Range
|
||||
const auto range = (int)(m_settings.rotation.range * 100);
|
||||
content_sizer->Add(new wxStaticText(box, wxID_ANY, _("Range")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
m_rotation_range = new wxSlider(box, wxID_ANY, range, 50, 200);
|
||||
content_sizer->Add(m_rotation_range, 1, wxALL | wxEXPAND, 5);
|
||||
|
||||
const auto range_text = new wxStaticText(box, wxID_ANY, wxString::Format("%d%%", range));
|
||||
content_sizer->Add(range_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
m_rotation_range->Bind(wxEVT_SLIDER, &DefaultControllerSettings::on_range_change, this, wxID_ANY, wxID_ANY,
|
||||
new wxControlObject(range_text));
|
||||
|
||||
content_sizer->AddSpacer(1);
|
||||
m_rotation_draw = new wxInputDraw(box, wxID_ANY, wxDefaultPosition, { 60, 60 });
|
||||
content_sizer->Add(m_rotation_draw, 0, wxTOP | wxBOTTOM | wxALIGN_CENTER, 5);
|
||||
|
||||
box_sizer->Add(content_sizer, 1, wxEXPAND, 0);
|
||||
}
|
||||
|
||||
row_sizer->Add(box_sizer, 0, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
|
||||
// Trigger
|
||||
{
|
||||
auto* box = new wxStaticBox(this, wxID_ANY, _("Trigger"));
|
||||
auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
|
||||
|
||||
{
|
||||
auto* content_sizer = new wxFlexGridSizer(0, 3, 0, 0);
|
||||
|
||||
// Deadzone
|
||||
const auto deadzone = (int)(m_settings.trigger.deadzone * 100);
|
||||
content_sizer->Add(new wxStaticText(box, wxID_ANY, _("Deadzone")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
m_trigger_deadzone = new wxSlider(box, wxID_ANY, deadzone, 0, 100);
|
||||
content_sizer->Add(m_trigger_deadzone, 1, wxALL | wxEXPAND, 5);
|
||||
|
||||
const auto deadzone_text = new wxStaticText(box, wxID_ANY, wxString::Format("%d%%", deadzone));
|
||||
content_sizer->Add(deadzone_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
m_trigger_deadzone->Bind(wxEVT_SLIDER, &DefaultControllerSettings::on_deadzone_change, this, wxID_ANY, wxID_ANY,
|
||||
new wxControlObject(deadzone_text));
|
||||
|
||||
|
||||
// Range
|
||||
const auto range = (int)(m_settings.trigger.range * 100);
|
||||
content_sizer->Add(new wxStaticText(box, wxID_ANY, _("Range")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
m_trigger_range = new wxSlider(box, wxID_ANY, range, 50, 200);
|
||||
content_sizer->Add(m_trigger_range, 1, wxALL | wxEXPAND, 5);
|
||||
|
||||
const auto range_text = new wxStaticText(box, wxID_ANY, wxString::Format("%d%%", range));
|
||||
content_sizer->Add(range_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
m_trigger_range->Bind(wxEVT_SLIDER, &DefaultControllerSettings::on_range_change, this, wxID_ANY, wxID_ANY,
|
||||
new wxControlObject(range_text));
|
||||
|
||||
content_sizer->AddSpacer(1);
|
||||
m_trigger_draw = new wxInputDraw(box, wxID_ANY, wxDefaultPosition, { 60, 60 });
|
||||
content_sizer->Add(m_trigger_draw, 0, wxTOP | wxBOTTOM | wxALIGN_CENTER, 5);
|
||||
|
||||
box_sizer->Add(content_sizer, 1, wxEXPAND, 0);
|
||||
}
|
||||
|
||||
row_sizer->Add(box_sizer, 0, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
sizer->Add(row_sizer);
|
||||
|
||||
{
|
||||
auto* control_sizer = new wxFlexGridSizer(0, 4, 0, 0);
|
||||
control_sizer->AddGrowableCol(3);
|
||||
|
||||
auto* ok_button = new wxButton(this, wxID_ANY, _("OK"));
|
||||
ok_button->Bind(wxEVT_BUTTON, [this](auto&) { update_settings(); EndModal(wxID_OK); });
|
||||
control_sizer->Add(ok_button, 0, wxALL, 5);
|
||||
|
||||
control_sizer->Add(0, 0, 0, wxEXPAND, 5);
|
||||
|
||||
auto* cancel_button = new wxButton(this, wxID_ANY, _("Cancel"));
|
||||
cancel_button->Bind(wxEVT_BUTTON, [this](auto&) { EndModal(wxID_CANCEL); });
|
||||
control_sizer->Add(cancel_button, 0, wxALL, 5);
|
||||
|
||||
auto* calibrate_button = new wxButton(this, wxID_ANY, _("Calibrate"));
|
||||
calibrate_button->Bind(wxEVT_BUTTON, [this](auto&) { m_controller->calibrate(); });
|
||||
control_sizer->Add(calibrate_button, 0, wxALL | wxALIGN_RIGHT, 5);
|
||||
|
||||
sizer->Add(control_sizer, 0, wxEXPAND, 5);
|
||||
}
|
||||
|
||||
|
||||
|
||||
this->SetSizer(sizer);
|
||||
this->Layout();
|
||||
this->Fit();
|
||||
|
||||
this->Bind(wxEVT_CLOSE_WINDOW, &DefaultControllerSettings::on_close, this);
|
||||
|
||||
m_timer = new wxTimer(this);
|
||||
Bind(wxEVT_TIMER, &DefaultControllerSettings::on_timer, this);
|
||||
m_timer->Start();
|
||||
}
|
||||
|
||||
DefaultControllerSettings::~DefaultControllerSettings()
|
||||
{
|
||||
m_controller->stop_rumble();
|
||||
m_timer->Stop();
|
||||
}
|
||||
|
||||
void DefaultControllerSettings::update_settings()
|
||||
{
|
||||
// update settings
|
||||
m_controller->set_settings(m_settings);
|
||||
if (m_use_motion)
|
||||
m_controller->set_use_motion(m_use_motion->GetValue());
|
||||
}
|
||||
|
||||
void DefaultControllerSettings::on_timer(wxTimerEvent& event)
|
||||
{
|
||||
if (m_rumble_time.has_value() && std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - m_rumble_time.value()).count() > 500 )
|
||||
{
|
||||
m_controller->stop_rumble();
|
||||
m_rumble_time = {};
|
||||
}
|
||||
|
||||
const auto& default_state = m_controller->get_default_state();
|
||||
|
||||
auto state = m_controller->raw_state();
|
||||
m_controller->apply_axis_setting(state.axis, default_state.axis, m_settings.axis);
|
||||
m_controller->apply_axis_setting(state.rotation, default_state.rotation, m_settings.rotation);
|
||||
m_controller->apply_axis_setting(state.trigger, default_state.trigger, m_settings.trigger);
|
||||
|
||||
m_axis_draw->SetDeadzone(m_settings.axis.deadzone);
|
||||
m_axis_draw->SetAxisValue(state.axis);
|
||||
|
||||
m_rotation_draw->SetDeadzone(m_settings.rotation.deadzone);
|
||||
m_rotation_draw->SetAxisValue(state.rotation);
|
||||
|
||||
m_trigger_draw->SetDeadzone(m_settings.trigger.deadzone);
|
||||
m_trigger_draw->SetAxisValue(state.trigger);
|
||||
}
|
||||
|
||||
void DefaultControllerSettings::on_close(wxCloseEvent& event)
|
||||
{
|
||||
if (this->GetReturnCode() == 0 || this->GetReturnCode() == wxID_OK)
|
||||
update_settings();
|
||||
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void DefaultControllerSettings::on_deadzone_change(wxCommandEvent& event)
|
||||
{
|
||||
update_slider_text(event);
|
||||
const auto new_value = (float)event.GetInt() / 100.0f;
|
||||
|
||||
if (event.GetEventObject() == m_axis_deadzone)
|
||||
m_settings.axis.deadzone = new_value;
|
||||
else if (event.GetEventObject() == m_rotation_deadzone)
|
||||
m_settings.rotation.deadzone = new_value;
|
||||
else if (event.GetEventObject() == m_trigger_deadzone)
|
||||
m_settings.trigger.deadzone = new_value;
|
||||
}
|
||||
|
||||
void DefaultControllerSettings::on_range_change(wxCommandEvent& event)
|
||||
{
|
||||
update_slider_text(event);
|
||||
|
||||
const auto new_value = (float)event.GetInt() / 100.0f;
|
||||
|
||||
if (event.GetEventObject() == m_axis_range)
|
||||
m_settings.axis.range = new_value;
|
||||
else if (event.GetEventObject() == m_rotation_range)
|
||||
m_settings.rotation.range = new_value;
|
||||
else if (event.GetEventObject() == m_trigger_range)
|
||||
m_settings.trigger.range = new_value;
|
||||
}
|
||||
|
||||
void DefaultControllerSettings::on_rumble_change(wxCommandEvent& event)
|
||||
{
|
||||
update_slider_text(event);
|
||||
|
||||
const auto rumble_value = event.GetInt();
|
||||
m_settings.rumble = (float)rumble_value / 100.0f;
|
||||
|
||||
m_controller->set_rumble(m_settings.rumble);
|
||||
if (rumble_value != 0)
|
||||
m_controller->start_rumble();
|
||||
else
|
||||
m_controller->stop_rumble();
|
||||
|
||||
m_controller->set_rumble(m_rumble_backup);
|
||||
|
||||
m_rumble_time = std::chrono::steady_clock::now();
|
||||
}
|
||||
42
src/gui/input/settings/DefaultControllerSettings.h
Normal file
42
src/gui/input/settings/DefaultControllerSettings.h
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#pragma once
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/timer.h>
|
||||
#include <wx/slider.h>
|
||||
|
||||
#include "input/api/Controller.h"
|
||||
|
||||
class wxCheckBox;
|
||||
class wxInputDraw;
|
||||
|
||||
class DefaultControllerSettings : public wxDialog
|
||||
{
|
||||
public:
|
||||
DefaultControllerSettings(wxWindow* parent, const wxPoint& position, ControllerPtr controller);
|
||||
~DefaultControllerSettings();
|
||||
|
||||
private:
|
||||
void update_settings();
|
||||
|
||||
ControllerPtr m_controller;
|
||||
ControllerBase::Settings m_settings;
|
||||
float m_rumble_backup;
|
||||
|
||||
wxTimer* m_timer;
|
||||
std::optional<std::chrono::steady_clock::time_point> m_rumble_time{};
|
||||
|
||||
wxSlider* m_axis_deadzone, *m_axis_range;
|
||||
wxSlider* m_rotation_deadzone, *m_rotation_range;
|
||||
wxSlider* m_trigger_deadzone, *m_trigger_range;
|
||||
wxSlider* m_rumble;
|
||||
|
||||
wxCheckBox* m_use_motion = nullptr;
|
||||
|
||||
wxInputDraw* m_axis_draw, * m_rotation_draw, *m_trigger_draw;
|
||||
|
||||
void on_timer(wxTimerEvent& event);
|
||||
void on_close(wxCloseEvent& event);
|
||||
void on_deadzone_change(wxCommandEvent& event);
|
||||
void on_range_change(wxCommandEvent& event);
|
||||
void on_rumble_change(wxCommandEvent& event);
|
||||
};
|
||||
369
src/gui/input/settings/WiimoteControllerSettings.cpp
Normal file
369
src/gui/input/settings/WiimoteControllerSettings.cpp
Normal file
|
|
@ -0,0 +1,369 @@
|
|||
#include "gui/input/settings/WiimoteControllerSettings.h"
|
||||
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/slider.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/gbsizer.h>
|
||||
#include <wx/statline.h>
|
||||
#include <wx/checkbox.h>
|
||||
#include <wx/statbox.h>
|
||||
|
||||
#include "gui/helpers/wxControlObject.h"
|
||||
#include "gui/helpers/wxHelpers.h"
|
||||
#include "gui/components/wxInputDraw.h"
|
||||
#include "gui/input/InputAPIAddWindow.h"
|
||||
|
||||
#if BOOST_OS_WINDOWS
|
||||
|
||||
WiimoteControllerSettings::WiimoteControllerSettings(wxWindow* parent, const wxPoint& position, std::shared_ptr<NativeWiimoteController> controller)
|
||||
: wxDialog(parent, wxID_ANY, _("Controller settings"), position, wxDefaultSize,
|
||||
wxDEFAULT_DIALOG_STYLE), m_controller(std::move(controller))
|
||||
{
|
||||
m_settings = m_controller->get_settings();
|
||||
m_rumble_backup = m_settings.rumble;
|
||||
m_packet_delay_backup = m_controller->get_packet_delay();
|
||||
|
||||
this->SetSizeHints(wxDefaultSize, wxDefaultSize);
|
||||
|
||||
auto* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
// extension info
|
||||
{
|
||||
auto* box = new wxStaticBox(this, wxID_ANY, _("Connected extension"));
|
||||
auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
|
||||
|
||||
m_extension_text = new wxStaticText(box, wxID_ANY, _("None"));
|
||||
box_sizer->Add(m_extension_text, 0, wxALL, 5);
|
||||
|
||||
sizer->Add(box_sizer, 0, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
|
||||
// options
|
||||
{
|
||||
auto* box = new wxStaticBox(this, wxID_ANY, _("Options"));
|
||||
auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
|
||||
|
||||
{
|
||||
auto* row_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
// Rumble
|
||||
m_rumble = new wxCheckBox(box, wxID_ANY, _("Rumble"));
|
||||
m_rumble->SetValue(m_settings.rumble > 0);
|
||||
row_sizer->Add(m_rumble, 0, wxALL, 5);
|
||||
|
||||
m_rumble->Bind(wxEVT_CHECKBOX, &WiimoteControllerSettings::on_rumble_change, this);
|
||||
|
||||
// Motion
|
||||
m_use_motion = new wxCheckBox(box, wxID_ANY, _("Use motion"));
|
||||
m_use_motion->SetValue(m_settings.motion);
|
||||
m_use_motion->Enable(m_controller->has_motion());
|
||||
row_sizer->Add(m_use_motion, 0, wxALL, 5);
|
||||
|
||||
box_sizer->Add(row_sizer);
|
||||
}
|
||||
{
|
||||
auto* row_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
// Delay
|
||||
row_sizer->Add(new wxStaticText(box, wxID_ANY, _("Packet delay")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
m_package_delay = new wxSlider(box, wxID_ANY, m_packet_delay_backup, 1, 100);
|
||||
row_sizer->Add(m_package_delay, 1, wxALL | wxEXPAND, 5);
|
||||
|
||||
const auto range_text = new wxStaticText(box, wxID_ANY, wxString::Format("%dms", m_packet_delay_backup));
|
||||
row_sizer->Add(range_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
m_package_delay->Bind(wxEVT_SLIDER, &WiimoteControllerSettings::on_delay_change, this, wxID_ANY, wxID_ANY,
|
||||
new wxControlObject(range_text));
|
||||
|
||||
box_sizer->Add(row_sizer);
|
||||
}
|
||||
|
||||
sizer->Add(box_sizer, 0, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
|
||||
// Nunchuck
|
||||
{
|
||||
m_nunchuck_settings = new wxStaticBox(this, wxID_ANY, _("Nunchuck"));
|
||||
auto* box_sizer = new wxStaticBoxSizer(m_nunchuck_settings, wxVERTICAL);
|
||||
|
||||
{
|
||||
auto* content_sizer = new wxFlexGridSizer(0, 3, 0, 0);
|
||||
|
||||
// Deadzone
|
||||
const auto deadzone = (int)(m_settings.axis.deadzone * 100);
|
||||
content_sizer->Add(new wxStaticText(m_nunchuck_settings, wxID_ANY, _("Deadzone")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
m_nunchuck_deadzone = new wxSlider(m_nunchuck_settings, wxID_ANY, deadzone, 0, 100);
|
||||
content_sizer->Add(m_nunchuck_deadzone, 1, wxALL | wxEXPAND, 5);
|
||||
|
||||
const auto deadzone_text = new wxStaticText(m_nunchuck_settings, wxID_ANY, wxString::Format("%d%%", deadzone));
|
||||
content_sizer->Add(deadzone_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
m_nunchuck_deadzone->Bind(wxEVT_SLIDER, &WiimoteControllerSettings::on_slider_change, this, wxID_ANY, wxID_ANY,
|
||||
new wxControlObject(deadzone_text));
|
||||
|
||||
|
||||
// Range
|
||||
const auto range = (int)(m_settings.axis.range * 100);
|
||||
content_sizer->Add(new wxStaticText(m_nunchuck_settings, wxID_ANY, _("Range")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
m_nunchuck_range = new wxSlider(m_nunchuck_settings, wxID_ANY, range, 50, 200);
|
||||
content_sizer->Add(m_nunchuck_range, 1, wxALL | wxEXPAND, 5);
|
||||
|
||||
const auto range_text = new wxStaticText(m_nunchuck_settings, wxID_ANY, wxString::Format("%d%%", range));
|
||||
content_sizer->Add(range_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
m_nunchuck_range->Bind(wxEVT_SLIDER, &WiimoteControllerSettings::on_slider_change, this, wxID_ANY, wxID_ANY,
|
||||
new wxControlObject(range_text));
|
||||
|
||||
content_sizer->AddSpacer(1);
|
||||
m_nunchuck_draw = new wxInputDraw(m_nunchuck_settings, wxID_ANY, wxDefaultPosition, { 60, 60 });
|
||||
content_sizer->Add(m_nunchuck_draw, 0, wxTOP | wxBOTTOM | wxALIGN_CENTER, 5);
|
||||
|
||||
box_sizer->Add(content_sizer, 1, wxEXPAND, 0);
|
||||
}
|
||||
|
||||
sizer->Add(box_sizer, 0, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
|
||||
// Classic
|
||||
{
|
||||
m_classic_settings = new wxStaticBox(this, wxID_ANY, _("Classic"));
|
||||
auto* box_sizer = new wxStaticBoxSizer(m_classic_settings, wxVERTICAL);
|
||||
|
||||
{
|
||||
auto* content_sizer = new wxFlexGridSizer(0, 6, 0, 0);
|
||||
|
||||
// Deadzone
|
||||
{
|
||||
// Axis
|
||||
const auto deadzone = (int)(m_settings.axis.deadzone * 100);
|
||||
content_sizer->Add(new wxStaticText(m_classic_settings, wxID_ANY, _("Deadzone")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
m_classic_axis_deadzone = new wxSlider(m_classic_settings, wxID_ANY, deadzone, 0, 100);
|
||||
content_sizer->Add(m_classic_axis_deadzone, 1, wxALL | wxEXPAND, 5);
|
||||
|
||||
const auto deadzone_text = new wxStaticText(m_classic_settings, wxID_ANY, wxString::Format("%d%%", deadzone));
|
||||
content_sizer->Add(deadzone_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
m_classic_axis_deadzone->Bind(wxEVT_SLIDER, &WiimoteControllerSettings::on_slider_change, this, wxID_ANY, wxID_ANY,
|
||||
new wxControlObject(deadzone_text));
|
||||
}
|
||||
{
|
||||
// Range
|
||||
const auto deadzone = (int)(m_settings.rotation.deadzone * 100);
|
||||
content_sizer->Add(new wxStaticText(m_classic_settings, wxID_ANY, _("Deadzone")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
m_classic_rotation_deadzone = new wxSlider(m_classic_settings, wxID_ANY, deadzone, 0, 100);
|
||||
content_sizer->Add(m_classic_rotation_deadzone, 1, wxALL | wxEXPAND, 5);
|
||||
|
||||
const auto deadzone_text = new wxStaticText(m_classic_settings, wxID_ANY, wxString::Format("%d%%", deadzone));
|
||||
content_sizer->Add(deadzone_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
m_classic_rotation_deadzone->Bind(wxEVT_SLIDER, &WiimoteControllerSettings::on_slider_change, this, wxID_ANY, wxID_ANY,
|
||||
new wxControlObject(deadzone_text));
|
||||
}
|
||||
|
||||
// Range
|
||||
{
|
||||
// Axis
|
||||
const auto range = (int)(m_settings.axis.range * 100);
|
||||
content_sizer->Add(new wxStaticText(m_classic_settings, wxID_ANY, _("Range")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
m_classic_axis_range = new wxSlider(m_classic_settings, wxID_ANY, range, 50, 200);
|
||||
content_sizer->Add(m_classic_axis_range, 1, wxALL | wxEXPAND, 5);
|
||||
|
||||
const auto range_text = new wxStaticText(m_classic_settings, wxID_ANY, wxString::Format("%d%%", range));
|
||||
content_sizer->Add(range_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
m_classic_axis_range->Bind(wxEVT_SLIDER, &WiimoteControllerSettings::on_slider_change, this, wxID_ANY, wxID_ANY,
|
||||
new wxControlObject(range_text));
|
||||
}
|
||||
{
|
||||
// Rotation
|
||||
const auto range = (int)(m_settings.rotation.range * 100);
|
||||
content_sizer->Add(new wxStaticText(m_classic_settings, wxID_ANY, _("Range")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
m_classic_rotation_range = new wxSlider(m_classic_settings, wxID_ANY, range, 50, 200);
|
||||
content_sizer->Add(m_classic_rotation_range, 1, wxALL | wxEXPAND, 5);
|
||||
|
||||
const auto range_text = new wxStaticText(m_classic_settings, wxID_ANY, wxString::Format("%d%%", range));
|
||||
content_sizer->Add(range_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
m_classic_rotation_range->Bind(wxEVT_SLIDER, &WiimoteControllerSettings::on_slider_change, this, wxID_ANY, wxID_ANY,
|
||||
new wxControlObject(range_text));
|
||||
}
|
||||
|
||||
content_sizer->AddSpacer(1);
|
||||
m_classic_axis_draw = new wxInputDraw(m_classic_settings, wxID_ANY, wxDefaultPosition, { 60, 60 });
|
||||
content_sizer->Add(m_classic_axis_draw, 0, wxTOP | wxBOTTOM | wxALIGN_CENTER, 5);
|
||||
content_sizer->AddSpacer(1);
|
||||
|
||||
content_sizer->AddSpacer(1);
|
||||
m_classic_rotation_draw = new wxInputDraw(m_classic_settings, wxID_ANY, wxDefaultPosition, { 60, 60 });
|
||||
content_sizer->Add(m_classic_rotation_draw, 0, wxTOP | wxBOTTOM | wxALIGN_CENTER, 5);
|
||||
|
||||
box_sizer->Add(content_sizer, 1, wxEXPAND, 0);
|
||||
}
|
||||
|
||||
sizer->Add(box_sizer, 0, wxALL | wxEXPAND, 5);
|
||||
}
|
||||
|
||||
{
|
||||
auto* control_sizer = new wxFlexGridSizer(0, 4, 0, 0);
|
||||
control_sizer->AddGrowableCol(3);
|
||||
|
||||
auto* ok_button = new wxButton(this, wxID_ANY, _("OK"));
|
||||
ok_button->Bind(wxEVT_BUTTON, [this](auto&) { update_settings(); EndModal(wxID_OK); });
|
||||
control_sizer->Add(ok_button, 0, wxALL, 5);
|
||||
|
||||
control_sizer->Add(0, 0, 0, wxEXPAND, 5);
|
||||
|
||||
auto* cancel_button = new wxButton(this, wxID_ANY, _("Cancel"));
|
||||
cancel_button->Bind(wxEVT_BUTTON, [this](auto&) { EndModal(wxID_CANCEL); });
|
||||
control_sizer->Add(cancel_button, 0, wxALL, 5);
|
||||
|
||||
auto* calibrate_button = new wxButton(this, wxID_ANY, _("Calibrate"));
|
||||
calibrate_button->Bind(wxEVT_BUTTON, [this](auto&) { m_controller->calibrate(); });
|
||||
control_sizer->Add(calibrate_button, 0, wxALL | wxALIGN_RIGHT, 5);
|
||||
|
||||
sizer->Add(control_sizer, 0, wxEXPAND, 5);
|
||||
}
|
||||
|
||||
|
||||
|
||||
this->SetSizer(sizer);
|
||||
this->Layout();
|
||||
this->Fit();
|
||||
|
||||
this->Bind(wxEVT_CLOSE_WINDOW, &WiimoteControllerSettings::on_close, this);
|
||||
|
||||
m_timer = new wxTimer(this);
|
||||
Bind(wxEVT_TIMER, &WiimoteControllerSettings::on_timer, this);
|
||||
m_timer->Start();
|
||||
}
|
||||
|
||||
WiimoteControllerSettings::~WiimoteControllerSettings()
|
||||
{
|
||||
m_controller->stop_rumble();
|
||||
m_timer->Stop();
|
||||
}
|
||||
|
||||
void WiimoteControllerSettings::update_settings()
|
||||
{
|
||||
// update settings
|
||||
m_controller->set_settings(m_settings);
|
||||
if (m_use_motion)
|
||||
m_controller->set_use_motion(m_use_motion->GetValue());
|
||||
|
||||
m_controller->set_packet_delay(m_package_delay->GetValue());
|
||||
}
|
||||
|
||||
void WiimoteControllerSettings::on_timer(wxTimerEvent& event)
|
||||
{
|
||||
if (m_rumble_time.has_value() && std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - m_rumble_time.value()).count() > 500)
|
||||
{
|
||||
m_controller->stop_rumble();
|
||||
m_rumble_time = {};
|
||||
}
|
||||
|
||||
const auto& default_state = m_controller->get_default_state();
|
||||
|
||||
auto state = m_controller->raw_state();
|
||||
m_controller->apply_axis_setting(state.axis, default_state.axis, m_settings.axis);
|
||||
m_controller->apply_axis_setting(state.rotation, default_state.rotation, m_settings.rotation);
|
||||
m_controller->apply_axis_setting(state.trigger, default_state.trigger, m_settings.trigger);
|
||||
|
||||
if (m_nunchuck_settings->IsEnabled())
|
||||
{
|
||||
m_nunchuck_draw->SetDeadzone(m_settings.axis.deadzone);
|
||||
m_nunchuck_draw->SetAxisValue(state.axis);
|
||||
}
|
||||
|
||||
if (m_classic_settings->IsEnabled())
|
||||
{
|
||||
m_classic_axis_draw->SetDeadzone(m_settings.axis.deadzone);
|
||||
m_classic_axis_draw->SetAxisValue(state.axis);
|
||||
|
||||
m_classic_rotation_draw->SetDeadzone(m_settings.rotation.deadzone);
|
||||
m_classic_rotation_draw->SetAxisValue(state.rotation);
|
||||
}
|
||||
|
||||
wxString label;
|
||||
switch (m_controller->get_extension())
|
||||
{
|
||||
case NativeWiimoteController::Nunchuck:
|
||||
label = _("Nunchuck");
|
||||
m_nunchuck_settings->Enable();
|
||||
m_classic_settings->Disable();
|
||||
break;
|
||||
case NativeWiimoteController::Classic:
|
||||
label = _("Classic");
|
||||
m_nunchuck_settings->Disable();
|
||||
m_classic_settings->Enable();
|
||||
break;
|
||||
default:
|
||||
m_nunchuck_settings->Disable();
|
||||
m_classic_settings->Disable();
|
||||
}
|
||||
|
||||
if(m_controller->is_mpls_attached())
|
||||
{
|
||||
const bool empty = label.empty();
|
||||
if (!empty)
|
||||
label.Append(" (");
|
||||
|
||||
label.Append(_("MotionPlus"));
|
||||
|
||||
if (!empty)
|
||||
label.Append(")");
|
||||
}
|
||||
|
||||
if(label.empty())
|
||||
{
|
||||
label = _("None");
|
||||
}
|
||||
|
||||
m_extension_text->SetLabelText(label);
|
||||
}
|
||||
|
||||
void WiimoteControllerSettings::on_close(wxCloseEvent& event)
|
||||
{
|
||||
if (this->GetReturnCode() == 0 || this->GetReturnCode() == wxID_OK)
|
||||
update_settings();
|
||||
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void WiimoteControllerSettings::on_slider_change(wxCommandEvent& event)
|
||||
{
|
||||
update_slider_text(event);
|
||||
const auto new_value = (float)event.GetInt() / 100.0f;
|
||||
|
||||
auto* obj = event.GetEventObject();
|
||||
if (obj == m_nunchuck_deadzone || obj == m_classic_axis_deadzone)
|
||||
m_settings.axis.deadzone = new_value;
|
||||
else if (obj == m_nunchuck_range || obj == m_classic_axis_range)
|
||||
m_settings.axis.range = new_value;
|
||||
else if (obj == m_classic_rotation_deadzone)
|
||||
m_settings.rotation.deadzone = new_value;
|
||||
else if (obj == m_classic_rotation_range)
|
||||
m_settings.rotation.range = new_value;
|
||||
}
|
||||
|
||||
void WiimoteControllerSettings::on_rumble_change(wxCommandEvent& event)
|
||||
{
|
||||
const auto rumble_value = m_rumble->GetValue();
|
||||
m_settings.rumble = rumble_value ? 1.0f : 0.0f;
|
||||
|
||||
m_controller->set_rumble(m_settings.rumble);
|
||||
if (rumble_value)
|
||||
m_controller->start_rumble();
|
||||
else
|
||||
m_controller->stop_rumble();
|
||||
|
||||
m_controller->set_rumble(m_rumble_backup);
|
||||
|
||||
m_rumble_time = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
void WiimoteControllerSettings::on_delay_change(wxCommandEvent& event)
|
||||
{
|
||||
update_slider_text(event, "%dms");
|
||||
}
|
||||
|
||||
#endif
|
||||
61
src/gui/input/settings/WiimoteControllerSettings.h
Normal file
61
src/gui/input/settings/WiimoteControllerSettings.h
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
#pragma once
|
||||
|
||||
#if BOOST_OS_WINDOWS
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/timer.h>
|
||||
#include <wx/slider.h>
|
||||
|
||||
#include "input/api/Controller.h"
|
||||
#include "input/api/Wiimote/NativeWiimoteController.h"
|
||||
|
||||
class wxStaticBox;
|
||||
class wxStaticText;
|
||||
class wxCheckBox;
|
||||
class wxInputDraw;
|
||||
|
||||
class WiimoteControllerSettings : public wxDialog
|
||||
{
|
||||
public:
|
||||
WiimoteControllerSettings(wxWindow* parent, const wxPoint& position, std::shared_ptr<NativeWiimoteController> controller);
|
||||
~WiimoteControllerSettings();
|
||||
|
||||
private:
|
||||
void update_settings();
|
||||
|
||||
std::shared_ptr<NativeWiimoteController> m_controller;
|
||||
ControllerBase::Settings m_settings;
|
||||
float m_rumble_backup;
|
||||
uint32 m_packet_delay_backup;
|
||||
|
||||
wxTimer* m_timer;
|
||||
std::optional<std::chrono::steady_clock::time_point> m_rumble_time{};
|
||||
|
||||
wxStaticText* m_extension_text;
|
||||
|
||||
wxSlider* m_package_delay;
|
||||
wxCheckBox* m_rumble = nullptr;
|
||||
wxCheckBox* m_use_motion = nullptr;
|
||||
|
||||
wxStaticBox* m_nunchuck_settings;
|
||||
wxSlider* m_nunchuck_deadzone, * m_nunchuck_range;
|
||||
wxInputDraw* m_nunchuck_draw;
|
||||
|
||||
wxStaticBox* m_classic_settings;
|
||||
wxSlider* m_classic_axis_deadzone, * m_classic_axis_range;
|
||||
wxInputDraw* m_classic_axis_draw;
|
||||
wxSlider* m_classic_rotation_deadzone, * m_classic_rotation_range;
|
||||
wxInputDraw* m_classic_rotation_draw;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void on_timer(wxTimerEvent& event);
|
||||
void on_close(wxCloseEvent& event);
|
||||
void on_slider_change(wxCommandEvent& event);
|
||||
void on_rumble_change(wxCommandEvent& event);
|
||||
void on_delay_change(wxCommandEvent& event);
|
||||
};
|
||||
|
||||
#endif
|
||||
379
src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp
Normal file
379
src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp
Normal file
|
|
@ -0,0 +1,379 @@
|
|||
#include "gui/wxgui.h"
|
||||
#include "Cafe/OS/libs/coreinit/coreinit_Thread.h"
|
||||
#include "Cafe/OS/libs/coreinit/coreinit_Scheduler.h"
|
||||
#include "DebugPPCThreadsWindow.h"
|
||||
#include "Cafe/OS/RPL/rpl.h"
|
||||
#include "Cafe/OS/RPL/rpl_symbol_storage.h"
|
||||
|
||||
enum
|
||||
{
|
||||
// options
|
||||
REFRESH_ID,
|
||||
AUTO_REFRESH_ID,
|
||||
CLOSE_ID,
|
||||
GPLIST_ID,
|
||||
|
||||
// list context menu options
|
||||
THREADLIST_MENU_BOOST_PRIO_1,
|
||||
THREADLIST_MENU_BOOST_PRIO_5,
|
||||
THREADLIST_MENU_DECREASE_PRIO_1,
|
||||
THREADLIST_MENU_DECREASE_PRIO_5,
|
||||
THREADLIST_MENU_SUSPEND,
|
||||
THREADLIST_MENU_RESUME,
|
||||
THREADLIST_MENU_DUMP_STACK_TRACE,
|
||||
};
|
||||
|
||||
wxBEGIN_EVENT_TABLE(DebugPPCThreadsWindow, wxFrame)
|
||||
EVT_BUTTON(CLOSE_ID,DebugPPCThreadsWindow::OnCloseButton)
|
||||
EVT_BUTTON(REFRESH_ID,DebugPPCThreadsWindow::OnRefreshButton)
|
||||
|
||||
EVT_CLOSE(DebugPPCThreadsWindow::OnClose)
|
||||
wxEND_EVENT_TABLE()
|
||||
|
||||
DebugPPCThreadsWindow::DebugPPCThreadsWindow(wxFrame& parent)
|
||||
: wxFrame(&parent, wxID_ANY, _("PPC threads"), wxDefaultPosition, wxSize(930, 280), wxCLOSE_BOX | wxCLIP_CHILDREN | wxCAPTION | wxRESIZE_BORDER)
|
||||
{
|
||||
wxFrame::SetBackgroundColour(*wxWHITE);
|
||||
|
||||
auto* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_thread_list = new wxListCtrl(this, GPLIST_ID, wxPoint(0, 0), wxSize(930, 240), wxLC_REPORT);
|
||||
|
||||
m_thread_list->SetFont(wxFont(8, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, "Courier New")); //wxSystemSettings::GetFont(wxSYS_OEM_FIXED_FONT));
|
||||
|
||||
// add columns
|
||||
wxListItem col0;
|
||||
col0.SetId(0);
|
||||
col0.SetText(_("Address"));
|
||||
col0.SetWidth(75);
|
||||
m_thread_list->InsertColumn(0, col0);
|
||||
wxListItem col1;
|
||||
col1.SetId(1);
|
||||
col1.SetText(_("Entry"));
|
||||
col1.SetWidth(75);
|
||||
m_thread_list->InsertColumn(1, col1);
|
||||
wxListItem col2;
|
||||
col2.SetId(2);
|
||||
col2.SetText(_("Stack"));
|
||||
col2.SetWidth(145);
|
||||
m_thread_list->InsertColumn(2, col2);
|
||||
wxListItem col3;
|
||||
col3.SetId(3);
|
||||
col3.SetText(_("PC"));
|
||||
col3.SetWidth(120);
|
||||
m_thread_list->InsertColumn(3, col3);
|
||||
wxListItem colLR;
|
||||
colLR.SetId(4);
|
||||
colLR.SetText(_("LR"));
|
||||
colLR.SetWidth(75);
|
||||
m_thread_list->InsertColumn(4, colLR);
|
||||
wxListItem col4;
|
||||
col4.SetId(5);
|
||||
col4.SetText(_("State"));
|
||||
col4.SetWidth(90);
|
||||
m_thread_list->InsertColumn(5, col4);
|
||||
wxListItem col5;
|
||||
col5.SetId(6);
|
||||
col5.SetText(_("Affinity"));
|
||||
col5.SetWidth(70);
|
||||
m_thread_list->InsertColumn(6, col5);
|
||||
wxListItem colPriority;
|
||||
colPriority.SetId(7);
|
||||
colPriority.SetText(_("Priority"));
|
||||
colPriority.SetWidth(80);
|
||||
m_thread_list->InsertColumn(7, colPriority);
|
||||
wxListItem col6;
|
||||
col6.SetId(8);
|
||||
col6.SetText(_("SliceStart"));
|
||||
col6.SetWidth(110);
|
||||
m_thread_list->InsertColumn(8, col6);
|
||||
wxListItem col7;
|
||||
col7.SetId(9);
|
||||
col7.SetText(_("SumWakeTime"));
|
||||
col7.SetWidth(110);
|
||||
m_thread_list->InsertColumn(9, col7);
|
||||
wxListItem col8;
|
||||
col8.SetId(10);
|
||||
col8.SetText(_("ThreadName"));
|
||||
col8.SetWidth(180);
|
||||
m_thread_list->InsertColumn(10, col8);
|
||||
wxListItem col9;
|
||||
col9.SetId(11);
|
||||
col9.SetText(_("GPR"));
|
||||
col9.SetWidth(180);
|
||||
m_thread_list->InsertColumn(11, col9);
|
||||
wxListItem col10;
|
||||
col10.SetId(12);
|
||||
col10.SetText(_("Extra info"));
|
||||
col10.SetWidth(180);
|
||||
m_thread_list->InsertColumn(12, col10);
|
||||
|
||||
sizer->Add(m_thread_list, 1, wxEXPAND | wxALL, 5);
|
||||
|
||||
auto* row = new wxBoxSizer(wxHORIZONTAL);
|
||||
wxButton* button = new wxButton(this, REFRESH_ID, _("Refresh"), wxPoint(0, 0), wxSize(80, 26));
|
||||
row->Add(button, 0, wxALL, 5);
|
||||
|
||||
m_auto_refresh = new wxCheckBox(this, AUTO_REFRESH_ID, _("Auto refresh"));
|
||||
m_auto_refresh->SetValue(true);
|
||||
row->Add(m_auto_refresh, 0, wxEXPAND | wxALL, 5);
|
||||
|
||||
sizer->Add(row, 0, wxEXPAND | wxALL, 5);
|
||||
|
||||
m_thread_list->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(DebugPPCThreadsWindow::OnThreadListRightClick), nullptr, this);
|
||||
|
||||
SetSizer(sizer);
|
||||
|
||||
RefreshThreadList();
|
||||
|
||||
m_timer = new wxTimer(this);
|
||||
this->Bind(wxEVT_TIMER, &DebugPPCThreadsWindow::OnTimer, this);
|
||||
m_timer->Start(250);
|
||||
}
|
||||
|
||||
DebugPPCThreadsWindow::~DebugPPCThreadsWindow()
|
||||
{
|
||||
m_timer->Stop();
|
||||
}
|
||||
|
||||
void DebugPPCThreadsWindow::OnCloseButton(wxCommandEvent& event)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
void DebugPPCThreadsWindow::OnRefreshButton(wxCommandEvent& event)
|
||||
{
|
||||
RefreshThreadList();
|
||||
}
|
||||
|
||||
void DebugPPCThreadsWindow::OnClose(wxCloseEvent& event)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
void DebugPPCThreadsWindow::OnTimer(wxTimerEvent& event)
|
||||
{
|
||||
if (m_auto_refresh->IsChecked())
|
||||
RefreshThreadList();
|
||||
}
|
||||
|
||||
|
||||
#define _r(__idx) _swapEndianU32(cafeThread->context.gpr[__idx])
|
||||
|
||||
void DebugPPCThreadsWindow::RefreshThreadList()
|
||||
{
|
||||
wxWindowUpdateLocker lock(m_thread_list);
|
||||
|
||||
long selected_thread = 0;
|
||||
const int selection = m_thread_list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
|
||||
if (selection != wxNOT_FOUND)
|
||||
selected_thread = m_thread_list->GetItemData(selection);
|
||||
|
||||
const int scrollPos = m_thread_list->GetScrollPos(0);
|
||||
m_thread_list->DeleteAllItems();
|
||||
|
||||
__OSLockScheduler();
|
||||
srwlock_activeThreadList.LockWrite();
|
||||
for (sint32 i = 0; i < activeThreadCount; i++)
|
||||
{
|
||||
MPTR threadItrMPTR = activeThread[i];
|
||||
OSThread_t* cafeThread = (OSThread_t*)memory_getPointerFromVirtualOffset(threadItrMPTR);
|
||||
|
||||
char tempStr[512];
|
||||
sprintf(tempStr, "%08X", threadItrMPTR);
|
||||
|
||||
|
||||
wxListItem item;
|
||||
item.SetId(i);
|
||||
item.SetText(tempStr);
|
||||
m_thread_list->InsertItem(item);
|
||||
m_thread_list->SetItemData(item, (long)threadItrMPTR);
|
||||
// entry point
|
||||
sprintf(tempStr, "%08X", _swapEndianU32(cafeThread->entrypoint));
|
||||
m_thread_list->SetItem(i, 1, tempStr);
|
||||
// stack base (low)
|
||||
sprintf(tempStr, "%08X - %08X", _swapEndianU32(cafeThread->stackEnd), _swapEndianU32(cafeThread->stackBase));
|
||||
m_thread_list->SetItem(i, 2, tempStr);
|
||||
// pc
|
||||
RPLStoredSymbol* symbol = rplSymbolStorage_getByAddress(cafeThread->context.srr0);
|
||||
if (symbol)
|
||||
sprintf(tempStr, "%s (0x%08x)", (const char*)symbol->symbolName, cafeThread->context.srr0);
|
||||
else
|
||||
sprintf(tempStr, "%08X", cafeThread->context.srr0);
|
||||
m_thread_list->SetItem(i, 3, tempStr);
|
||||
// lr
|
||||
sprintf(tempStr, "%08X", _swapEndianU32(cafeThread->context.lr));
|
||||
m_thread_list->SetItem(i, 4, tempStr);
|
||||
// state
|
||||
OSThread_t::THREAD_STATE threadState = cafeThread->state;
|
||||
wxString threadStateStr = "UNDEFINED";
|
||||
if (cafeThread->suspendCounter != 0)
|
||||
threadStateStr = "SUSPENDED";
|
||||
else if (threadState == OSThread_t::THREAD_STATE::STATE_NONE)
|
||||
threadStateStr = "NONE";
|
||||
else if (threadState == OSThread_t::THREAD_STATE::STATE_READY)
|
||||
threadStateStr = "READY";
|
||||
else if (threadState == OSThread_t::THREAD_STATE::STATE_RUNNING)
|
||||
threadStateStr = "RUNNING";
|
||||
else if (threadState == OSThread_t::THREAD_STATE::STATE_WAITING)
|
||||
threadStateStr = "WAITING";
|
||||
else if (threadState == OSThread_t::THREAD_STATE::STATE_MORIBUND)
|
||||
threadStateStr = "MORIBUND";
|
||||
m_thread_list->SetItem(i, 5, threadStateStr);
|
||||
// affinity
|
||||
uint8 affinity = cafeThread->attr&7;
|
||||
uint8 affinityReal = cafeThread->context.affinity;
|
||||
if(affinity != affinityReal)
|
||||
sprintf(tempStr, "(!) %d%d%d real: %d%d%d", (affinity >> 0) & 1, (affinity >> 1) & 1, (affinity >> 2) & 1, (affinityReal >> 0) & 1, (affinityReal >> 1) & 1, (affinityReal >> 2) & 1);
|
||||
else
|
||||
sprintf(tempStr, "%d%d%d", (affinity >> 0) & 1, (affinity >> 1) & 1, (affinity >> 2) & 1);
|
||||
m_thread_list->SetItem(i, 6, tempStr);
|
||||
// priority
|
||||
sint32 effectivePriority = cafeThread->effectivePriority;
|
||||
sprintf(tempStr, "%d", effectivePriority);
|
||||
m_thread_list->SetItem(i, 7, tempStr);
|
||||
// last awake in cycles
|
||||
uint64 lastWakeUpTime = cafeThread->wakeUpTime;
|
||||
sprintf(tempStr, "%I64u", lastWakeUpTime);
|
||||
m_thread_list->SetItem(i, 8, tempStr);
|
||||
// awake time in cycles
|
||||
uint64 awakeTime = cafeThread->totalCycles;
|
||||
sprintf(tempStr, "%I64u", awakeTime);
|
||||
m_thread_list->SetItem(i, 9, tempStr);
|
||||
// thread name
|
||||
const char* threadName = "NULL";
|
||||
if (!cafeThread->threadName.IsNull())
|
||||
threadName = cafeThread->threadName.GetPtr();
|
||||
m_thread_list->SetItem(i, 10, threadName);
|
||||
// GPR
|
||||
sprintf(tempStr, "r3 %08x r4 %08x r5 %08x r6 %08x r7 %08x", _r(3), _r(4), _r(5), _r(6), _r(7));
|
||||
m_thread_list->SetItem(i, 11, tempStr);
|
||||
// waiting condition / extra info
|
||||
coreinit::OSMutex* mutex = cafeThread->waitingForMutex;
|
||||
if (mutex)
|
||||
sprintf(tempStr, "Mutex 0x%08x (Held by thread 0x%08X Lock-Count: %d)", memory_getVirtualOffsetFromPointer(mutex), mutex->owner.GetMPTR(), (uint32)mutex->lockCount);
|
||||
else
|
||||
sprintf(tempStr, "");
|
||||
|
||||
// OSSetThreadCancelState
|
||||
if (cafeThread->requestFlags & OSThread_t::REQUEST_FLAG_CANCEL)
|
||||
strcat(tempStr, "[Cancel requested]");
|
||||
|
||||
m_thread_list->SetItem(i, 12, tempStr);
|
||||
|
||||
if(selected_thread != 0 && selected_thread == (long)threadItrMPTR)
|
||||
m_thread_list->SetItemState(i, wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED, wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED);
|
||||
}
|
||||
srwlock_activeThreadList.UnlockWrite();
|
||||
__OSUnlockScheduler();
|
||||
|
||||
m_thread_list->SetScrollPos(0, scrollPos, true);
|
||||
}
|
||||
|
||||
void DebugLogStackTrace(OSThread_t* thread, MPTR sp);
|
||||
|
||||
void DebugPPCThreadsWindow::DumpStackTrace(OSThread_t* thread)
|
||||
{
|
||||
cemuLog_log(LogType::Force, fmt::format("Dumping stack trace for thread {0:08x} LR: {1:08x}", memory_getVirtualOffsetFromPointer(thread), _swapEndianU32(thread->context.lr)));
|
||||
DebugLogStackTrace(thread, _swapEndianU32(thread->context.gpr[1]));
|
||||
}
|
||||
|
||||
void DebugPPCThreadsWindow::OnThreadListPopupClick(wxCommandEvent& evt)
|
||||
{
|
||||
MPTR threadMPTR = (MPTR)(size_t)static_cast<wxMenu *>(evt.GetEventObject())->GetClientData();
|
||||
// check if thread is still active
|
||||
bool threadIsActive = false;
|
||||
srwlock_activeThreadList.LockWrite();
|
||||
for (sint32 i = 0; i < activeThreadCount; i++)
|
||||
{
|
||||
MPTR threadItrMPTR = activeThread[i];
|
||||
if (threadItrMPTR == threadMPTR)
|
||||
{
|
||||
threadIsActive = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
srwlock_activeThreadList.UnlockWrite();
|
||||
if (threadIsActive == false)
|
||||
return;
|
||||
// handle command
|
||||
OSThread_t* osThread = (OSThread_t*)memory_getPointerFromVirtualOffset(threadMPTR);
|
||||
switch (evt.GetId())
|
||||
{
|
||||
case THREADLIST_MENU_BOOST_PRIO_5:
|
||||
osThread->basePriority = osThread->basePriority - 5;
|
||||
break;
|
||||
case THREADLIST_MENU_BOOST_PRIO_1:
|
||||
osThread->basePriority = osThread->basePriority - 1;
|
||||
break;
|
||||
case THREADLIST_MENU_DECREASE_PRIO_5:
|
||||
osThread->basePriority = osThread->basePriority + 5;
|
||||
break;
|
||||
case THREADLIST_MENU_DECREASE_PRIO_1:
|
||||
osThread->basePriority = osThread->basePriority + 1;
|
||||
break;
|
||||
case THREADLIST_MENU_SUSPEND:
|
||||
coreinit::OSSuspendThread(osThread);
|
||||
break;
|
||||
case THREADLIST_MENU_RESUME:
|
||||
coreinit::OSResumeThread(osThread);
|
||||
break;
|
||||
case THREADLIST_MENU_DUMP_STACK_TRACE:
|
||||
DumpStackTrace(osThread);
|
||||
break;
|
||||
}
|
||||
coreinit::__OSUpdateThreadEffectivePriority(osThread);
|
||||
// update thread list
|
||||
RefreshThreadList();
|
||||
}
|
||||
|
||||
void DebugPPCThreadsWindow::OnThreadListRightClick(wxMouseEvent& event)
|
||||
{
|
||||
// Get the item index
|
||||
int hitTestFlag;
|
||||
int itemIndex = m_thread_list->HitTest(event.GetPosition(), hitTestFlag);
|
||||
if (itemIndex == wxNOT_FOUND)
|
||||
return;
|
||||
// select item
|
||||
m_thread_list->SetItemState(itemIndex, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
|
||||
long sel = m_thread_list->GetNextItem(-1, wxLIST_NEXT_ALL,
|
||||
wxLIST_STATE_SELECTED);
|
||||
if (sel != -1)
|
||||
m_thread_list->SetItemState(sel, 0, wxLIST_STATE_SELECTED);
|
||||
m_thread_list->SetItemState(itemIndex, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
|
||||
// check if thread is still on the list of active threads
|
||||
MPTR threadMPTR = (MPTR)m_thread_list->GetItemData(itemIndex);
|
||||
bool threadIsActive = false;
|
||||
srwlock_activeThreadList.LockWrite();
|
||||
for (sint32 i = 0; i < activeThreadCount; i++)
|
||||
{
|
||||
MPTR threadItrMPTR = activeThread[i];
|
||||
if (threadItrMPTR == threadMPTR)
|
||||
{
|
||||
threadIsActive = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
srwlock_activeThreadList.UnlockWrite();
|
||||
if (threadIsActive == false)
|
||||
return;
|
||||
// create menu entry
|
||||
wxMenu menu;
|
||||
menu.SetClientData((void*)(size_t)threadMPTR);
|
||||
menu.Append(THREADLIST_MENU_BOOST_PRIO_5, _("Boost priority (-5)"));
|
||||
menu.Append(THREADLIST_MENU_BOOST_PRIO_1, _("Boost priority (-1)"));
|
||||
menu.AppendSeparator();
|
||||
menu.Append(THREADLIST_MENU_DECREASE_PRIO_5, _("Decrease priority (+5)"));
|
||||
menu.Append(THREADLIST_MENU_DECREASE_PRIO_1, _("Decrease priority (+1)"));
|
||||
menu.AppendSeparator();
|
||||
menu.Append(THREADLIST_MENU_RESUME, _("Resume"));
|
||||
menu.Append(THREADLIST_MENU_SUSPEND, _("Suspend"));
|
||||
menu.AppendSeparator();
|
||||
menu.Append(THREADLIST_MENU_DUMP_STACK_TRACE, _("Write stack trace to log"));
|
||||
menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(DebugPPCThreadsWindow::OnThreadListPopupClick), nullptr, this);
|
||||
PopupMenu(&menu);
|
||||
}
|
||||
|
||||
void DebugPPCThreadsWindow::Close()
|
||||
{
|
||||
this->Destroy();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue