mirror of
https://github.com/cemu-project/Cemu.git
synced 2026-06-05 06:04:58 -06:00
Merge branch 'main' into vulkan-opengl-options
This commit is contained in:
commit
4eb92d20b2
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@ -282,7 +282,6 @@ jobs:
|
|||||||
mv bin/Cemu_release.app bin/Cemu_app/Cemu.app
|
mv bin/Cemu_release.app bin/Cemu_app/Cemu.app
|
||||||
mv bin/Cemu_app/Cemu.app/Contents/MacOS/Cemu_release bin/Cemu_app/Cemu.app/Contents/MacOS/Cemu
|
mv bin/Cemu_app/Cemu.app/Contents/MacOS/Cemu_release bin/Cemu_app/Cemu.app/Contents/MacOS/Cemu
|
||||||
sed -i '' 's/Cemu_release/Cemu/g' bin/Cemu_app/Cemu.app/Contents/Info.plist
|
sed -i '' 's/Cemu_release/Cemu/g' bin/Cemu_app/Cemu.app/Contents/Info.plist
|
||||||
chmod a+x bin/Cemu_app/Cemu.app/Contents/MacOS/{Cemu,update.sh}
|
|
||||||
codesign --entitlements ${{github.workspace}}/src/resource/cemu.macos.entitlements --force --deep --preserve-metadata=entitlements,requirements,flags,runtime --sign - --timestamp --options runtime bin/Cemu_app/Cemu.app/Contents/MacOS/Cemu
|
codesign --entitlements ${{github.workspace}}/src/resource/cemu.macos.entitlements --force --deep --preserve-metadata=entitlements,requirements,flags,runtime --sign - --timestamp --options runtime bin/Cemu_app/Cemu.app/Contents/MacOS/Cemu
|
||||||
ln -s /Applications bin/Cemu_app/Applications
|
ln -s /Applications bin/Cemu_app/Applications
|
||||||
hdiutil create ./bin/tmp.dmg -ov -volname "Cemu" -fs HFS+ -srcfolder "./bin/Cemu_app"
|
hdiutil create ./bin/tmp.dmg -ov -volname "Cemu" -fs HFS+ -srcfolder "./bin/Cemu_app"
|
||||||
|
|||||||
6
BUILD.md
6
BUILD.md
@ -164,7 +164,7 @@ To install the dependencies required to build Cemu, you will need to install Hom
|
|||||||
|
|
||||||
The following dependencies are required. You can install them using Homebrew with the following command:
|
The following dependencies are required. You can install them using Homebrew with the following command:
|
||||||
|
|
||||||
`brew install git cmake ninja nasm automake libtool boost`
|
`brew install automake boost cmake git libtool nasm ninja pkgconf`
|
||||||
|
|
||||||
### MoltenVK
|
### MoltenVK
|
||||||
|
|
||||||
@ -189,6 +189,10 @@ Alternatively, you can use the non-privateapi version of MoltenVK, but you may e
|
|||||||
4. `cmake --build build`
|
4. `cmake --build build`
|
||||||
5. You should now have a Cemu executable file in the /bin folder, which you can run using `./bin/Cemu_release`.
|
5. You should now have a Cemu executable file in the /bin folder, which you can run using `./bin/Cemu_release`.
|
||||||
|
|
||||||
|
#### Creating an app bundle
|
||||||
|
- If you want to create an app bundle instead of a raw executable, append the following flag to the command in step 3:
|
||||||
|
- `-DMACOS_BUNDLE=ON`
|
||||||
|
|
||||||
#### Troubleshooting steps
|
#### Troubleshooting steps
|
||||||
- If step 3 gives you an error about not being able to find ninja, try appending the following to the command and try again:
|
- If step 3 gives you an error about not being able to find ninja, try appending the following to the command and try again:
|
||||||
- **On an Apple Silicon Mac:** `-DCMAKE_MAKE_PROGRAM=/opt/homebrew/bin/ninja`
|
- **On an Apple Silicon Mac:** `-DCMAKE_MAKE_PROGRAM=/opt/homebrew/bin/ninja`
|
||||||
|
|||||||
@ -93,6 +93,10 @@ if (MSVC)
|
|||||||
add_compile_options($<$<CONFIG:Release,RelWithDebInfo>:/Ot>) # favor speed
|
add_compile_options($<$<CONFIG:Release,RelWithDebInfo>:/Ot>) # favor speed
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(CMAKE_GENERATOR STREQUAL "Xcode")
|
||||||
|
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY XCODE_STARTUP_PROJECT CemuBin)
|
||||||
|
endif()
|
||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
enable_language(OBJC OBJCXX)
|
enable_language(OBJC OBJCXX)
|
||||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "13.4")
|
set(CMAKE_OSX_DEPLOYMENT_TARGET "13.4")
|
||||||
@ -250,7 +254,7 @@ if(CEMU_ARCHITECTURE MATCHES "(aarch64)|(AARCH64)|(arm64)|(ARM64)")
|
|||||||
add_subdirectory("dependencies/xbyak_aarch64" EXCLUDE_FROM_ALL)
|
add_subdirectory("dependencies/xbyak_aarch64" EXCLUDE_FROM_ALL)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(ZArchive)
|
find_package(ZArchive QUIET)
|
||||||
if (NOT ZArchive_FOUND)
|
if (NOT ZArchive_FOUND)
|
||||||
add_subdirectory("dependencies/ZArchive" EXCLUDE_FROM_ALL)
|
add_subdirectory("dependencies/ZArchive" EXCLUDE_FROM_ALL)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@ -87,6 +87,12 @@ if(WIN32)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(APPLE AND CMAKE_GENERATOR STREQUAL "Xcode")
|
||||||
|
set_target_properties(CemuBin PROPERTIES
|
||||||
|
XCODE_ATTRIBUTE_ENABLE_DEBUG_DYLIB[variant=Debug] "YES"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
string(TIMESTAMP CURRENT_YEAR "%Y" UTC)
|
string(TIMESTAMP CURRENT_YEAR "%Y" UTC)
|
||||||
|
|
||||||
set_property(TARGET CemuBin PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
set_property(TARGET CemuBin PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||||
@ -118,7 +124,7 @@ if (MACOS_BUNDLE)
|
|||||||
set(FOLDERS gameProfiles resources)
|
set(FOLDERS gameProfiles resources)
|
||||||
foreach(folder ${FOLDERS})
|
foreach(folder ${FOLDERS})
|
||||||
add_custom_command (TARGET CemuBin POST_BUILD
|
add_custom_command (TARGET CemuBin POST_BUILD
|
||||||
COMMAND ${CMAKE_COMMAND} ARGS -E copy_directory "${CMAKE_SOURCE_DIR}/bin/${folder}" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/SharedSupport/${folder}")
|
COMMAND ${CMAKE_COMMAND} ARGS -E copy_directory "${CMAKE_SOURCE_DIR}/bin/${folder}" "$<TARGET_BUNDLE_DIR:CemuBin>/Contents/SharedSupport/${folder}")
|
||||||
endforeach(folder)
|
endforeach(folder)
|
||||||
|
|
||||||
if (ENABLE_VULKAN)
|
if (ENABLE_VULKAN)
|
||||||
@ -129,14 +135,40 @@ if (MACOS_BUNDLE)
|
|||||||
else()
|
else()
|
||||||
message(FATAL_ERROR "failed to find libMoltenVK.dylib")
|
message(FATAL_ERROR "failed to find libMoltenVK.dylib")
|
||||||
endif ()
|
endif ()
|
||||||
|
endif()
|
||||||
|
|
||||||
add_custom_command (TARGET CemuBin POST_BUILD
|
set(UPDATE_SH_PATH "${CMAKE_SOURCE_DIR}/src/resource/update.sh")
|
||||||
COMMAND ${CMAKE_COMMAND} ARGS -E copy "${MOLTENVK_PATH}" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libMoltenVK.dylib")
|
set(APP_BUNDLE_DIR "$<TARGET_BUNDLE_DIR:CemuBin>")
|
||||||
endif()
|
set(FRAMEWORKS_DIR "${APP_BUNDLE_DIR}/Contents/Frameworks")
|
||||||
|
set(RESOURCES_DIR "${APP_BUNDLE_DIR}/Contents/Resources")
|
||||||
|
|
||||||
add_custom_command (TARGET CemuBin POST_BUILD
|
add_custom_command(TARGET CemuBin POST_BUILD
|
||||||
COMMAND ${CMAKE_COMMAND} ARGS -E copy "${CMAKE_SOURCE_DIR}/src/resource/update.sh" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/update.sh"
|
if (ENABLE_VULKAN)
|
||||||
COMMAND bash -c "install_name_tool -add_rpath @executable_path/../Frameworks ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}")
|
COMMAND ${CMAKE_COMMAND} -E copy
|
||||||
|
"${MOLTENVK_PATH}"
|
||||||
|
"${FRAMEWORKS_DIR}/libMoltenVK.dylib"
|
||||||
|
endif()
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy
|
||||||
|
"${LIBUSB_PATH}"
|
||||||
|
"${FRAMEWORKS_DIR}/libusb-1.0.0.dylib"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy
|
||||||
|
"${UPDATE_SH_PATH}"
|
||||||
|
"${RESOURCES_DIR}/update.sh"
|
||||||
|
COMMAND chmod 755
|
||||||
|
"${RESOURCES_DIR}/update.sh"
|
||||||
|
)
|
||||||
|
|
||||||
|
set_target_properties(CemuBin PROPERTIES
|
||||||
|
BUILD_WITH_INSTALL_RPATH TRUE
|
||||||
|
INSTALL_RPATH "@executable_path/../Frameworks"
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_command(TARGET CemuBin POST_BUILD
|
||||||
|
COMMAND install_name_tool
|
||||||
|
-change @rpath/libusb-1.0.0.dylib
|
||||||
|
@executable_path/../Frameworks/libusb-1.0.0.dylib
|
||||||
|
"$<TARGET_FILE:CemuBin>"
|
||||||
|
)
|
||||||
else()
|
else()
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
if (ENABLE_VULKAN)
|
if (ENABLE_VULKAN)
|
||||||
|
|||||||
@ -170,7 +170,7 @@ void padscoreExport_WPADRead(PPCInterpreter_t* hCPU)
|
|||||||
{
|
{
|
||||||
ppcDefineParamU32(channel, 0);
|
ppcDefineParamU32(channel, 0);
|
||||||
ppcDefineParamPtr(wpadStatus, WPADStatus_t, 1);
|
ppcDefineParamPtr(wpadStatus, WPADStatus_t, 1);
|
||||||
cemuLog_log(LogType::InputAPI, "WPADRead({}, {:x})", channel, fmt::ptr(wpadStatus));
|
cemuLog_log(LogType::InputAPI, "WPADRead({}, {:x})", channel, memory_getVirtualOffsetFromPointer(wpadStatus));
|
||||||
|
|
||||||
if (channel < InputManager::kMaxWPADControllers)
|
if (channel < InputManager::kMaxWPADControllers)
|
||||||
{
|
{
|
||||||
@ -224,7 +224,7 @@ void padscoreExport_WPADGetInfo(PPCInterpreter_t* hCPU)
|
|||||||
{
|
{
|
||||||
ppcDefineParamU32(channel, 0);
|
ppcDefineParamU32(channel, 0);
|
||||||
ppcDefineParamStructPtr(wpadInfo, WPADInfo_t, 1);
|
ppcDefineParamStructPtr(wpadInfo, WPADInfo_t, 1);
|
||||||
cemuLog_log(LogType::InputAPI, "WPADGetInfo({}, 0x{:08x})", channel, fmt::ptr(wpadInfo));
|
cemuLog_log(LogType::InputAPI, "WPADGetInfo({}, 0x{:08x})", channel, memory_getVirtualOffsetFromPointer(wpadInfo));
|
||||||
|
|
||||||
if (channel < InputManager::kMaxWPADControllers)
|
if (channel < InputManager::kMaxWPADControllers)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -78,43 +78,22 @@ bool cemuLog_log(LogType type, std::string_view text);
|
|||||||
bool cemuLog_log(LogType type, std::u8string_view text);
|
bool cemuLog_log(LogType type, std::u8string_view text);
|
||||||
void cemuLog_waitForFlush(); // wait until all log lines are written
|
void cemuLog_waitForFlush(); // wait until all log lines are written
|
||||||
|
|
||||||
template<typename T, typename ... TArgs>
|
template<typename... TArgs>
|
||||||
bool cemuLog_log(LogType type, std::basic_string<T> formatStr, TArgs&&... args)
|
bool cemuLog_log(LogType type, fmt::format_string<TArgs...> formatStr, TArgs&&... args)
|
||||||
{
|
{
|
||||||
if (!cemuLog_isLoggingEnabled(type))
|
if (!cemuLog_isLoggingEnabled(type))
|
||||||
return false;
|
return false;
|
||||||
if constexpr (sizeof...(TArgs) == 0)
|
|
||||||
{
|
|
||||||
cemuLog_log(type, std::basic_string_view<T>(formatStr.data(), formatStr.size()));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const auto format_view = fmt::basic_string_view<T>(formatStr);
|
|
||||||
#if FMT_VERSION >= 110000
|
|
||||||
const auto text = fmt::vformat(format_view, fmt::make_format_args<fmt::buffered_context<T>>(args...));
|
|
||||||
#else
|
|
||||||
const auto text = fmt::vformat(format_view, fmt::make_format_args<fmt::buffer_context<T>>(args...));
|
|
||||||
#endif
|
|
||||||
cemuLog_log(type, std::basic_string_view(text.data(), text.size()));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T, typename ... TArgs>
|
cemuLog_log(type, fmt::format(formatStr, std::forward<TArgs>(args)...));
|
||||||
bool cemuLog_log(LogType type, const T* format, TArgs&&... args)
|
|
||||||
{
|
return true;
|
||||||
if (!cemuLog_isLoggingEnabled(type))
|
|
||||||
return false;
|
|
||||||
auto format_str = std::basic_string<T>(format);
|
|
||||||
return cemuLog_log(type, format_str, std::forward<TArgs>(args)...);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define cemuLog_logOnce(...) { static bool _not_first_call = false; if (!_not_first_call) { _not_first_call = true; cemuLog_log(__VA_ARGS__); } }
|
#define cemuLog_logOnce(...) { static bool _not_first_call = false; if (!_not_first_call) { _not_first_call = true; cemuLog_log(__VA_ARGS__); } }
|
||||||
|
|
||||||
// same as cemuLog_log, but only outputs in debug mode
|
// same as cemuLog_log, but only outputs in debug mode
|
||||||
template<typename TFmt, typename ... TArgs>
|
template<typename ... TArgs>
|
||||||
bool cemuLog_logDebug(LogType type, TFmt format, TArgs&&... args)
|
bool cemuLog_logDebug(LogType type, fmt::format_string<TArgs...> format, TArgs&&... args)
|
||||||
{
|
{
|
||||||
#ifdef CEMU_DEBUG_ASSERT
|
#ifdef CEMU_DEBUG_ASSERT
|
||||||
return cemuLog_log(type, format, std::forward<TArgs>(args)...);
|
return cemuLog_log(type, format, std::forward<TArgs>(args)...);
|
||||||
@ -123,6 +102,15 @@ bool cemuLog_logDebug(LogType type, TFmt format, TArgs&&... args)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool cemuLog_logDebug(LogType type, std::string_view message)
|
||||||
|
{
|
||||||
|
#ifdef CEMU_DEBUG_ASSERT
|
||||||
|
return cemuLog_log(type, message);
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#define cemuLog_logDebugOnce(...) { static bool _not_first_call = false; if (!_not_first_call) { _not_first_call = true; cemuLog_logDebug(__VA_ARGS__); } }
|
#define cemuLog_logDebugOnce(...) { static bool _not_first_call = false; if (!_not_first_call) { _not_first_call = true; cemuLog_logDebug(__VA_ARGS__); } }
|
||||||
|
|
||||||
// utility function for logging binary data as a hex dump
|
// utility function for logging binary data as a hex dump
|
||||||
|
|||||||
@ -46,6 +46,8 @@ add_library(CemuWxGui STATIC
|
|||||||
AudioDebuggerWindow.h
|
AudioDebuggerWindow.h
|
||||||
DownloadGraphicPacksWindow.cpp
|
DownloadGraphicPacksWindow.cpp
|
||||||
DownloadGraphicPacksWindow.h
|
DownloadGraphicPacksWindow.h
|
||||||
|
DownloadCustomGraphicPackWindow.cpp
|
||||||
|
DownloadCustomGraphicPackWindow.h
|
||||||
GameProfileWindow.cpp
|
GameProfileWindow.cpp
|
||||||
GameProfileWindow.h
|
GameProfileWindow.h
|
||||||
GameUpdateWindow.cpp
|
GameUpdateWindow.cpp
|
||||||
|
|||||||
@ -616,7 +616,8 @@ void CemuUpdateWindow::OnClose(wxCloseEvent& event)
|
|||||||
{
|
{
|
||||||
const auto tmppath = fs::temp_directory_path() / L"cemu_update/Cemu.dmg";
|
const auto tmppath = fs::temp_directory_path() / L"cemu_update/Cemu.dmg";
|
||||||
fs::path exePath = ActiveSettings::GetExecutablePath().parent_path();
|
fs::path exePath = ActiveSettings::GetExecutablePath().parent_path();
|
||||||
const auto apppath = exePath / L"update.sh";
|
const auto appResources = exePath.parent_path().parent_path() / L"Resources";
|
||||||
|
const auto apppath = appResources / L"update.sh";
|
||||||
execlp("sh", "sh", apppath.c_str(), NULL);
|
execlp("sh", "sh", apppath.c_str(), NULL);
|
||||||
|
|
||||||
exit(0);
|
exit(0);
|
||||||
|
|||||||
424
src/gui/wxgui/DownloadCustomGraphicPackWindow.cpp
Normal file
424
src/gui/wxgui/DownloadCustomGraphicPackWindow.cpp
Normal file
@ -0,0 +1,424 @@
|
|||||||
|
#include "wxgui/DownloadCustomGraphicPackWindow.h"
|
||||||
|
#include "Cafe/CafeSystem.h"
|
||||||
|
#include "config/ActiveSettings.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <wx/event.h>
|
||||||
|
#include <wx/gauge.h>
|
||||||
|
#include <wx/button.h>
|
||||||
|
#include <wx/gdicmn.h>
|
||||||
|
#include <wx/msgdlg.h>
|
||||||
|
#include <wx/sizer.h>
|
||||||
|
#include <wx/timer.h>
|
||||||
|
|
||||||
|
#include <curl/curl.h>
|
||||||
|
#include <zip.h>
|
||||||
|
|
||||||
|
static size_t curlDownloadFile_writeData(void *ptr, size_t size, size_t nmemb, DownloadCustomGraphicPackWindow::curlDownloadFileState_t* downloadState)
|
||||||
|
{
|
||||||
|
const size_t writeSize = size * nmemb;
|
||||||
|
const size_t currentSize = downloadState->fileData.size();
|
||||||
|
const size_t newSize = currentSize + writeSize;
|
||||||
|
auto* bytePtr = static_cast<const uint8*>(ptr);
|
||||||
|
downloadState->fileData.insert(downloadState->fileData.end(), bytePtr, bytePtr + writeSize);
|
||||||
|
return writeSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int progress_callback(DownloadCustomGraphicPackWindow::curlDownloadFileState_t* downloadState, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
|
||||||
|
{
|
||||||
|
if (downloadState->isCanceled)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (dltotal > 1.0)
|
||||||
|
downloadState->progress = dlnow / dltotal;
|
||||||
|
else
|
||||||
|
downloadState->progress = 0.0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool curlDownloadFile(const char *url, DownloadCustomGraphicPackWindow::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_XFERINFOFUNCTION, progress_callback);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, downloadState);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 30L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 15L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, BUILD_VERSION_WITH_NAME_STRING);
|
||||||
|
downloadState->fileData.resize(0);
|
||||||
|
const CURLcode res = curl_easy_perform(curl);
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
return res == CURLE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadCustomGraphicPackWindow::DownloadCustomGraphicPackWindow(wxWindow* parent)
|
||||||
|
: wxDialog(parent, wxID_ANY, _("Download Graphic Pack from URL"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX),
|
||||||
|
m_stage(StageDone), m_currentStage(StageDone)
|
||||||
|
{
|
||||||
|
auto* sizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
|
||||||
|
m_urlField = new wxTextCtrl(this, wxID_ANY, wxEmptyString);
|
||||||
|
m_urlField->SetHint(_("Enter download URL..."));
|
||||||
|
sizer->Add(m_urlField, 0, wxALL | wxEXPAND, 5);
|
||||||
|
|
||||||
|
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* buttonSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
|
||||||
|
m_statusText = new wxStaticText(this, wxID_ANY, _("Ready..."));
|
||||||
|
buttonSizer->Add(m_statusText, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||||
|
|
||||||
|
buttonSizer->AddStretchSpacer(1);
|
||||||
|
|
||||||
|
auto* m_closeButton = new wxButton(this, wxID_ANY, _("Close"));
|
||||||
|
m_closeButton->Bind(wxEVT_BUTTON, &DownloadCustomGraphicPackWindow::OnCancelButton, this);
|
||||||
|
buttonSizer->Add(m_closeButton, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||||
|
|
||||||
|
m_downloadButton = new wxButton(this, wxID_ANY, _("Download"));
|
||||||
|
m_downloadButton->Bind(wxEVT_BUTTON, &DownloadCustomGraphicPackWindow::OnDownloadButton, this);
|
||||||
|
buttonSizer->Add(m_downloadButton, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
||||||
|
|
||||||
|
sizer->Add(buttonSizer, 0, wxEXPAND | wxALL, 5);
|
||||||
|
|
||||||
|
this->SetSizer(sizer);
|
||||||
|
this->Centre(wxBOTH);
|
||||||
|
|
||||||
|
wxWindowBase::Layout();
|
||||||
|
wxWindowBase::Fit();
|
||||||
|
|
||||||
|
m_timer = new wxTimer(this);
|
||||||
|
this->Bind(wxEVT_TIMER, &DownloadCustomGraphicPackWindow::OnUpdate, this);
|
||||||
|
this->Bind(wxEVT_CLOSE_WINDOW, &DownloadCustomGraphicPackWindow::OnClose, this);
|
||||||
|
m_timer->Start(100);
|
||||||
|
|
||||||
|
m_downloadState = std::make_unique<curlDownloadFileState_t>();
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadCustomGraphicPackWindow::~DownloadCustomGraphicPackWindow()
|
||||||
|
{
|
||||||
|
if (m_downloadState)
|
||||||
|
m_downloadState->isCanceled = true;
|
||||||
|
|
||||||
|
m_timer->Stop();
|
||||||
|
if (m_thread.joinable())
|
||||||
|
m_thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
int DownloadCustomGraphicPackWindow::ShowModal()
|
||||||
|
{
|
||||||
|
if (CafeSystem::IsTitleRunning())
|
||||||
|
{
|
||||||
|
wxMessageBox(_("Graphic packs cannot be updated while a game is running."), _("Graphic packs"), 5, this);
|
||||||
|
return wxID_CANCEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
wxDialog::ShowModal();
|
||||||
|
return wxID_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadCustomGraphicPackWindow::OnClose(wxCloseEvent& event)
|
||||||
|
{
|
||||||
|
if (m_downloadState)
|
||||||
|
{
|
||||||
|
m_downloadState->isCanceled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_timer->Stop();
|
||||||
|
if (m_thread.joinable())
|
||||||
|
m_thread.join();
|
||||||
|
|
||||||
|
event.Skip();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadCustomGraphicPackWindow::OnUpdate(const wxTimerEvent& event)
|
||||||
|
{
|
||||||
|
if (m_currentStage >= StageDone)
|
||||||
|
{
|
||||||
|
m_downloadButton->Enable();
|
||||||
|
m_urlField->Enable();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_downloadButton->Disable();
|
||||||
|
m_urlField->Disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_currentStage != m_stage)
|
||||||
|
{
|
||||||
|
wxString status = "...";
|
||||||
|
const wxColour* colour = wxWHITE;
|
||||||
|
|
||||||
|
switch (m_stage)
|
||||||
|
{
|
||||||
|
case StageDownloading:
|
||||||
|
status = "Downloading...";
|
||||||
|
colour = wxWHITE;
|
||||||
|
break;
|
||||||
|
case StageVerifying:
|
||||||
|
status = "Verifying...";
|
||||||
|
colour = wxWHITE;
|
||||||
|
break;
|
||||||
|
case StageExtracting:
|
||||||
|
status = "Extracting...";
|
||||||
|
colour = wxWHITE;
|
||||||
|
break;
|
||||||
|
case StageDone:
|
||||||
|
status = "Done!";
|
||||||
|
colour = wxGREEN;
|
||||||
|
m_processBar->SetValue(100.0);
|
||||||
|
break;
|
||||||
|
case StageErrConnectFailed:
|
||||||
|
if (m_urlField->GetValue().empty())
|
||||||
|
{
|
||||||
|
status = "Please enter a valid URL.";
|
||||||
|
colour = wxWHITE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
status = "ERROR: Connection failed.";
|
||||||
|
colour = wxRED;
|
||||||
|
}
|
||||||
|
m_processBar->SetValue(0.0);
|
||||||
|
break;
|
||||||
|
case StageErrInvalidPack:
|
||||||
|
status = "ERROR: Invalid pack.";
|
||||||
|
colour = wxRED;
|
||||||
|
m_processBar->SetValue(0.0);
|
||||||
|
break;
|
||||||
|
case StageErrSourceFailed:
|
||||||
|
status = "ERROR: Failed to create ZIP source.";
|
||||||
|
colour = wxRED;
|
||||||
|
m_processBar->SetValue(0.0);
|
||||||
|
break;
|
||||||
|
case StageErrOpenFailed:
|
||||||
|
status = "ERROR: Failed to open downloaded ZIP.";
|
||||||
|
colour = wxRED;
|
||||||
|
m_processBar->SetValue(0.0);
|
||||||
|
break;
|
||||||
|
case StageErrConflict:
|
||||||
|
status = "ERROR: File conflict. Pack already installed?";
|
||||||
|
colour = wxRED;
|
||||||
|
m_processBar->SetValue(0.0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_currentStage = m_stage;
|
||||||
|
m_statusText->SetLabel(status);
|
||||||
|
m_statusText->SetForegroundColour(*colour);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadCustomGraphicPackWindow::OnCancelButton(const wxCommandEvent& event)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadCustomGraphicPackWindow::OnDownloadButton(const wxCommandEvent& event)
|
||||||
|
{
|
||||||
|
m_downloadButton->Disable();
|
||||||
|
m_urlField->Disable();
|
||||||
|
|
||||||
|
if (m_thread.joinable())
|
||||||
|
m_thread.join();
|
||||||
|
|
||||||
|
m_thread = std::thread(&DownloadCustomGraphicPackWindow::UpdateThread, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadCustomGraphicPackWindow::UpdateThread()
|
||||||
|
{
|
||||||
|
m_stage = StageDownloading;
|
||||||
|
if (curlDownloadFile(m_urlField->GetValue(), m_downloadState.get()) == false)
|
||||||
|
{
|
||||||
|
m_stage = StageErrConnectFailed;
|
||||||
|
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(), 0, &error)) == NULL)
|
||||||
|
{
|
||||||
|
zip_error_fini(&error);
|
||||||
|
m_stage = StageErrSourceFailed;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// open zip from source
|
||||||
|
if ((za = zip_open_from_source(src, 0, &error)) == NULL)
|
||||||
|
{
|
||||||
|
zip_source_free(src);
|
||||||
|
zip_error_fini(&error);
|
||||||
|
m_stage = StageErrOpenFailed;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto path = ActiveSettings::GetUserDataPath("graphicPacks/customGraphicPacks");
|
||||||
|
fs::create_directories(path);
|
||||||
|
|
||||||
|
// check if zip root directly contains a rules.txt
|
||||||
|
zip_stat_t zs;
|
||||||
|
zip_stat_init(&zs);
|
||||||
|
bool hasRootRulesTxt = (zip_stat(za, "rules.txt", 0, &zs) == 0);
|
||||||
|
|
||||||
|
fs::path extractionRoot = path;
|
||||||
|
|
||||||
|
if (hasRootRulesTxt)
|
||||||
|
{
|
||||||
|
// make a folder and extract into that
|
||||||
|
wxString urlStr = m_urlField->GetValue();
|
||||||
|
std::string folderName = "NewCustomPack"; // fallback
|
||||||
|
|
||||||
|
int lastSlash = urlStr.Find('/', true);
|
||||||
|
wxString fileNameBase = (lastSlash != wxNOT_FOUND) ? urlStr.Mid(lastSlash + 1) : urlStr;
|
||||||
|
|
||||||
|
int lastDot = fileNameBase.Find('.', true);
|
||||||
|
if (lastDot != wxNOT_FOUND)
|
||||||
|
{
|
||||||
|
fileNameBase = fileNameBase.Left(lastDot);
|
||||||
|
}
|
||||||
|
|
||||||
|
fileNameBase.Trim(true).Trim(false);
|
||||||
|
if (!fileNameBase.IsEmpty())
|
||||||
|
{
|
||||||
|
folderName = fileNameBase.ToStdString();
|
||||||
|
}
|
||||||
|
|
||||||
|
extractionRoot = path / folderName;
|
||||||
|
fs::create_directories(extractionRoot);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// not a gfxpack
|
||||||
|
m_stage = StageErrInvalidPack;
|
||||||
|
zip_close(za);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract
|
||||||
|
bool err = false;
|
||||||
|
zip_int64_t numEntries = zip_get_num_entries(za, 0);
|
||||||
|
for (zip_int64_t i = 0; i < numEntries; i++)
|
||||||
|
{
|
||||||
|
m_extractionProgress = (double)i / (double)numEntries;
|
||||||
|
|
||||||
|
if (m_downloadState->isCanceled)
|
||||||
|
{
|
||||||
|
err = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
zip_stat_t sb = { 0 };
|
||||||
|
zip_stat_init(&sb);
|
||||||
|
if (zip_stat_index(za, i, 0, &sb) != 0)
|
||||||
|
{
|
||||||
|
assert_dbg();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string fileName = sb.name;
|
||||||
|
if (std::strstr(sb.name, "../") != nullptr ||
|
||||||
|
std::strstr(sb.name, "..\\") != nullptr ||
|
||||||
|
(!fileName.empty() && (fileName[0] == '/' || fileName[0] == '\\')))
|
||||||
|
{
|
||||||
|
// bad path, CommunityGraphicPacks silently continues
|
||||||
|
// but this is a low-trust environment, so we halt
|
||||||
|
m_stage = StageErrInvalidPack;
|
||||||
|
err = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path targetDest = extractionRoot / fileName;
|
||||||
|
|
||||||
|
if (!fileName.empty() && fileName.back() == '/')
|
||||||
|
{
|
||||||
|
fs::create_directories(targetDest);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs::exists(targetDest) && fs::is_regular_file(targetDest))
|
||||||
|
{
|
||||||
|
m_stage = StageErrConflict;
|
||||||
|
err = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::create_directories(targetDest.parent_path());
|
||||||
|
|
||||||
|
zip_file_t* zf = zip_fopen_index(za, i, 0);
|
||||||
|
if (!zf)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sb.size > 1000000000) // 1GB suspicious limit per file
|
||||||
|
{
|
||||||
|
m_stage = StageErrInvalidPack;
|
||||||
|
err = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8> fileBuffer(sb.size);
|
||||||
|
|
||||||
|
if (zip_fread(zf, fileBuffer.data(), sb.size) == sb.size)
|
||||||
|
{
|
||||||
|
std::unique_ptr<FileStream> fs(FileStream::createFile2(targetDest));
|
||||||
|
if (fs)
|
||||||
|
{
|
||||||
|
fs->writeData(fileBuffer.data(), sb.size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
err = true;
|
||||||
|
m_stage = StageErrInvalidPack;
|
||||||
|
}
|
||||||
|
|
||||||
|
zip_fclose(zf);
|
||||||
|
}
|
||||||
|
|
||||||
|
zip_discard(za);
|
||||||
|
zip_error_fini(&error);
|
||||||
|
|
||||||
|
if (!err)
|
||||||
|
{
|
||||||
|
m_stage = StageDone;
|
||||||
|
}
|
||||||
|
}
|
||||||
66
src/gui/wxgui/DownloadCustomGraphicPackWindow.h
Normal file
66
src/gui/wxgui/DownloadCustomGraphicPackWindow.h
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <wx/dialog.h>
|
||||||
|
#include <wx/event.h>
|
||||||
|
#include <wx/gauge.h>
|
||||||
|
#include <wx/stattext.h>
|
||||||
|
#include <wx/timer.h>
|
||||||
|
#include <wx/textctrl.h>
|
||||||
|
|
||||||
|
struct curlDownloadFileState_t;
|
||||||
|
|
||||||
|
class DownloadCustomGraphicPackWindow : public wxDialog
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DownloadCustomGraphicPackWindow(wxWindow* parent);
|
||||||
|
~DownloadCustomGraphicPackWindow() override;
|
||||||
|
|
||||||
|
int ShowModal() override;
|
||||||
|
void OnClose(wxCloseEvent& event);
|
||||||
|
void OnUpdate(const wxTimerEvent& event);
|
||||||
|
void OnCancelButton(const wxCommandEvent& event);
|
||||||
|
void OnDownloadButton(const wxCommandEvent& event);
|
||||||
|
|
||||||
|
public:
|
||||||
|
struct curlDownloadFileState_t
|
||||||
|
{
|
||||||
|
std::vector<uint8> fileData;
|
||||||
|
std::atomic<double> progress = 0.0;
|
||||||
|
std::atomic<bool> isCanceled = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
void UpdateThread();
|
||||||
|
|
||||||
|
enum DownloadStage_t
|
||||||
|
{
|
||||||
|
StageDownloading,
|
||||||
|
StageExtracting,
|
||||||
|
StageVerifying,
|
||||||
|
StageDone,
|
||||||
|
|
||||||
|
// Error stages
|
||||||
|
StageErrConnectFailed,
|
||||||
|
StageErrSourceFailed,
|
||||||
|
StageErrInvalidPack,
|
||||||
|
StageErrOpenFailed,
|
||||||
|
StageErrConflict,
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<curlDownloadFileState_t> m_downloadState;
|
||||||
|
std::atomic<DownloadStage_t> m_stage;
|
||||||
|
DownloadStage_t m_currentStage;
|
||||||
|
std::atomic<double> m_extractionProgress;
|
||||||
|
|
||||||
|
wxTimer* m_timer;
|
||||||
|
wxTextCtrl* m_urlField;
|
||||||
|
wxGauge* m_processBar;
|
||||||
|
wxStaticText* m_statusText;
|
||||||
|
wxButton* m_downloadButton;
|
||||||
|
std::thread m_thread;
|
||||||
|
};
|
||||||
@ -2,6 +2,7 @@
|
|||||||
#include "wxgui/wxgui.h"
|
#include "wxgui/wxgui.h"
|
||||||
#include "wxgui/GraphicPacksWindow2.h"
|
#include "wxgui/GraphicPacksWindow2.h"
|
||||||
#include "wxgui/DownloadGraphicPacksWindow.h"
|
#include "wxgui/DownloadGraphicPacksWindow.h"
|
||||||
|
#include "wxgui/DownloadCustomGraphicPackWindow.h"
|
||||||
#include "Cafe/GraphicPack/GraphicPack2.h"
|
#include "Cafe/GraphicPack/GraphicPack2.h"
|
||||||
#include "config/CemuConfig.h"
|
#include "config/CemuConfig.h"
|
||||||
#include "config/ActiveSettings.h"
|
#include "config/ActiveSettings.h"
|
||||||
@ -302,6 +303,11 @@ GraphicPacksWindow2::GraphicPacksWindow2(wxWindow* parent, uint64_t title_id_fil
|
|||||||
sizer->Add(new wxStaticLine(m_right_panel, wxID_ANY), 0, wxLEFT|wxRIGHT | wxEXPAND, 3);
|
sizer->Add(new wxStaticLine(m_right_panel, wxID_ANY), 0, wxLEFT|wxRIGHT | wxEXPAND, 3);
|
||||||
|
|
||||||
auto* row = new wxBoxSizer(wxHORIZONTAL);
|
auto* row = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
|
||||||
|
m_download_from_url = new wxButton(m_right_panel, wxID_ANY, _("Download pack from URL"));
|
||||||
|
m_download_from_url->Bind(wxEVT_BUTTON, &GraphicPacksWindow2::OnClickCustomDownload, this);
|
||||||
|
row->Add(m_download_from_url, 0, wxALL, 5);
|
||||||
|
|
||||||
m_update_graphicPacks = new wxButton(m_right_panel, wxID_ANY, _("Download latest community graphic packs"));
|
m_update_graphicPacks = new wxButton(m_right_panel, wxID_ANY, _("Download latest community graphic packs"));
|
||||||
m_update_graphicPacks->Bind(wxEVT_BUTTON, &GraphicPacksWindow2::OnCheckForUpdates, this);
|
m_update_graphicPacks->Bind(wxEVT_BUTTON, &GraphicPacksWindow2::OnCheckForUpdates, this);
|
||||||
row->Add(m_update_graphicPacks, 0, wxALL, 5);
|
row->Add(m_update_graphicPacks, 0, wxALL, 5);
|
||||||
@ -599,6 +605,16 @@ void GraphicPacksWindow2::OnReloadShaders(wxCommandEvent& event)
|
|||||||
ReloadPack(m_shown_graphic_pack);
|
ReloadPack(m_shown_graphic_pack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GraphicPacksWindow2::OnClickCustomDownload(wxCommandEvent& event)
|
||||||
|
{
|
||||||
|
DownloadCustomGraphicPackWindow frame(this);
|
||||||
|
if (frame.ShowModal() == wxID_OK && !CafeSystem::IsTitleRunning())
|
||||||
|
{
|
||||||
|
RefreshGraphicPacks();
|
||||||
|
FillGraphicPackList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GraphicPacksWindow2::OnCheckForUpdates(wxCommandEvent& event)
|
void GraphicPacksWindow2::OnCheckForUpdates(wxCommandEvent& event)
|
||||||
{
|
{
|
||||||
DownloadGraphicPacksWindow frame(this);
|
DownloadGraphicPacksWindow frame(this);
|
||||||
|
|||||||
@ -46,6 +46,7 @@ private:
|
|||||||
wxBoxSizer* m_preset_sizer;
|
wxBoxSizer* m_preset_sizer;
|
||||||
std::vector<wxChoice*> m_active_preset;
|
std::vector<wxChoice*> m_active_preset;
|
||||||
wxButton* m_reload_shaders;
|
wxButton* m_reload_shaders;
|
||||||
|
wxButton* m_download_from_url;
|
||||||
wxButton* m_update_graphicPacks;
|
wxButton* m_update_graphicPacks;
|
||||||
wxInfoBar* m_info_bar;
|
wxInfoBar* m_info_bar;
|
||||||
|
|
||||||
@ -64,6 +65,7 @@ private:
|
|||||||
void OnTreeChoiceChanged(wxTreeEvent& event);
|
void OnTreeChoiceChanged(wxTreeEvent& event);
|
||||||
void OnActivePresetChanged(wxCommandEvent& event);
|
void OnActivePresetChanged(wxCommandEvent& event);
|
||||||
void OnReloadShaders(wxCommandEvent& event);
|
void OnReloadShaders(wxCommandEvent& event);
|
||||||
|
void OnClickCustomDownload(wxCommandEvent& event);
|
||||||
void OnCheckForUpdates(wxCommandEvent& event);
|
void OnCheckForUpdates(wxCommandEvent& event);
|
||||||
void OnSizeChanged(wxSizeEvent& event);
|
void OnSizeChanged(wxSizeEvent& event);
|
||||||
void SashPositionChanged(wxEvent& event);
|
void SashPositionChanged(wxEvent& event);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user